From f61b1968e07a3ba2399bc814249d6a8ba5e80d39 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gustavo=20I=C3=B1iguez=20Goya?= Date: Mon, 6 Mar 2023 12:37:24 +0100 Subject: [PATCH] Import opensnitch_1.5.8.1.orig.tar.gz [dgit import orig opensnitch_1.5.8.1.orig.tar.gz] --- .github/FUNDING.yml | 12 + .github/ISSUE_TEMPLATE/bug_report.md | 52 + .github/ISSUE_TEMPLATE/config.yml | 4 + .github/ISSUE_TEMPLATE/feature-request.md | 15 + .github/workflows/debian-package.yml | 57 + .github/workflows/ebpf.yml | 48 + .github/workflows/go.yml | 49 + .gitignore | 4 + LICENSE | 674 +++++ Makefile | 47 + README.md | 24 + daemon/.gitignore | 2 + daemon/Gopkg.toml | 19 + daemon/Makefile | 21 + daemon/conman/connection.go | 266 ++ daemon/conman/connection_test.go | 127 + daemon/core/core.go | 68 + daemon/core/system.go | 23 + daemon/core/version.go | 9 + daemon/default-config.json | 17 + daemon/dns/parse.go | 21 + daemon/dns/track.go | 99 + daemon/firewall/common/common.go | 102 + daemon/firewall/config/config.go | 199 ++ daemon/firewall/iptables/iptables.go | 138 + daemon/firewall/iptables/monitor.go | 62 + daemon/firewall/iptables/rules.go | 77 + daemon/firewall/iptables/system.go | 89 + daemon/firewall/nftables/monitor.go | 55 + daemon/firewall/nftables/nftables.go | 141 + daemon/firewall/nftables/rules.go | 201 ++ daemon/firewall/nftables/system.go | 40 + daemon/firewall/rules.go | 85 + daemon/go.mod | 16 + daemon/log/log.go | 212 ++ daemon/main.go | 415 +++ daemon/netfilter/packet.go | 57 + daemon/netfilter/queue.c | 2 + daemon/netfilter/queue.go | 242 ++ daemon/netfilter/queue.h | 113 + daemon/netlink/socket.go | 153 + daemon/netlink/socket_linux.go | 264 ++ daemon/netlink/socket_test.go | 116 + daemon/netstat/entry.go | 32 + daemon/netstat/find.go | 51 + daemon/netstat/parse.go | 120 + daemon/opensnitch.spec | 97 + daemon/opensnitchd.service | 16 + daemon/procmon/activepids.go | 89 + daemon/procmon/activepids_test.go | 104 + daemon/procmon/audit/client.go | 355 +++ daemon/procmon/audit/parse.go | 298 ++ daemon/procmon/cache.go | 339 +++ daemon/procmon/cache_test.go | 103 + daemon/procmon/details.go | 197 ++ daemon/procmon/ebpf/cache.go | 118 + daemon/procmon/ebpf/debug.go | 102 + daemon/procmon/ebpf/ebpf.go | 188 ++ daemon/procmon/ebpf/find.go | 171 ++ daemon/procmon/ebpf/monitor.go | 127 + daemon/procmon/ebpf/utils.go | 124 + daemon/procmon/find.go | 108 + daemon/procmon/find_test.go | 42 + daemon/procmon/monitor/init.go | 79 + daemon/procmon/parse.go | 134 + daemon/procmon/process.go | 112 + daemon/procmon/process_test.go | 135 + daemon/rule/loader.go | 418 +++ daemon/rule/loader_test.go | 275 ++ daemon/rule/operator.go | 297 ++ daemon/rule/operator_lists.go | 263 ++ daemon/rule/operator_test.go | 742 +++++ daemon/rule/rule.go | 115 + daemon/rule/rule_test.go | 47 + daemon/rule/testdata/000-allow-chrome.json | 16 + daemon/rule/testdata/001-deny-chrome.json | 16 + daemon/rule/testdata/invalid-regexp-list.json | 31 + daemon/rule/testdata/invalid-regexp.json | 16 + .../testdata/lists/domains/domainlists.txt | 4 + daemon/rule/testdata/lists/ips/ips.txt | 7 + daemon/rule/testdata/lists/nets/nets.txt | 8 + .../testdata/lists/regexp/domainsregexp.txt | 4 + .../live_reload/test-live-reload-delete.json | 16 + .../live_reload/test-live-reload-remove.json | 16 + daemon/statistics/event.go | 32 + daemon/statistics/stats.go | 244 ++ daemon/system-fw.json | 14 + daemon/ui/client.go | 343 +++ daemon/ui/config.go | 118 + daemon/ui/notifications.go | 304 ++ daemon/ui/protocol/.gitkeep | 0 debian/changelog | 233 ++ debian/control | 95 + debian/copyright | 32 + debian/gbp.conf | 2 + debian/gitlab-ci.yml | 27 + debian/opensnitch.init | 78 + debian/opensnitch.install | 3 + debian/opensnitch.logrotate | 13 + debian/opensnitch.service | 16 + debian/python3-opensnitch-ui.postinst | 18 + debian/python3-opensnitch-ui.postrm | 15 + debian/rules | 42 + debian/source/format | 1 + debian/source/options | 1 + debian/tests/control | 2 + debian/tests/test-resources.sh | 13 + debian/upstream/metadata | 9 + debian/watch | 4 + ebpf_prog/Makefile | 159 ++ ebpf_prog/README | 29 + ebpf_prog/arm-clang-asm-fix.patch | 14 + ebpf_prog/file.patch | 11 + ebpf_prog/opensnitch.c | 508 ++++ proto/.gitignore | 1 + proto/Makefile | 14 + proto/ui.proto | 129 + release.sh | 28 + .../opensnitch-ui-general-tab-deny.png | Bin 0 -> 109267 bytes screenshots/opensnitch-ui-proc-details.png | Bin 0 -> 76358 bytes screenshots/screenshot.png | Bin 0 -> 453311 bytes ui/.gitignore | 5 + ui/LICENSE | 28 + ui/MANIFEST.in | 3 + ui/Makefile | 17 + ui/bin/opensnitch-ui | 101 + ui/i18n/Makefile | 37 + ui/i18n/README.md | 37 + ui/i18n/generate_i18n.sh | 17 + ui/i18n/locales/de_DE/opensnitch-de_DE.ts | 2384 ++++++++++++++++ ui/i18n/locales/es_ES/opensnitch-es_ES.ts | 2435 ++++++++++++++++ ui/i18n/locales/eu_ES/opensnitch-eu_ES.ts | 1878 +++++++++++++ ui/i18n/locales/fr_FR/opensnitch-fr_FR.ts | 2420 ++++++++++++++++ ui/i18n/locales/hu_HU/opensnitch-hu_HU.ts | 2256 +++++++++++++++ ui/i18n/locales/ja_JP/opensnitch-ja_JP.ts | 2164 +++++++++++++++ ui/i18n/locales/lt_LT/opensnitch-lt_LT.ts | 2408 ++++++++++++++++ ui/i18n/locales/nb_NO/opensnitch-nb_NO.ts | 2390 ++++++++++++++++ ui/i18n/locales/pt_BR/opensnitch-pt_BR.ts | 2248 +++++++++++++++ ui/i18n/locales/ro_RO/opensnitch-ro_RO.ts | 1732 ++++++++++++ ui/i18n/locales/ru_RU/opensnitch-ru_RU.ts | 2458 ++++++++++++++++ ui/i18n/locales/tr_TR/opensnitch-tr_TR.ts | 2459 +++++++++++++++++ ui/i18n/opensnitch_i18n.pro | 33 + ui/opensnitch-ui.spec | 110 + ui/opensnitch/__init__.py | 0 ui/opensnitch/config.py | 159 ++ ui/opensnitch/customwidgets/__init__.py | 0 .../customwidgets/addresstablemodel.py | 69 + .../customwidgets/generictableview.py | 323 +++ ui/opensnitch/customwidgets/main.py | 535 ++++ ui/opensnitch/database.py | 439 +++ ui/opensnitch/desktop_parser.py | 178 ++ ui/opensnitch/dialogs/__init__.py | 0 ui/opensnitch/dialogs/preferences.py | 553 ++++ ui/opensnitch/dialogs/processdetails.py | 329 +++ ui/opensnitch/dialogs/prompt.py | 588 ++++ ui/opensnitch/dialogs/ruleseditor.py | 810 ++++++ ui/opensnitch/dialogs/stats.py | 2041 ++++++++++++++ ui/opensnitch/nodes.py | 308 +++ ui/opensnitch/notifications.py | 126 + ui/opensnitch/res/__init__.py | 0 ui/opensnitch/res/icon-alert.png | Bin 0 -> 19598 bytes ui/opensnitch/res/icon-off.png | Bin 0 -> 20195 bytes ui/opensnitch/res/icon-pause.png | Bin 0 -> 16530 bytes ui/opensnitch/res/icon-pause.svg | 109 + ui/opensnitch/res/icon-red.png | Bin 0 -> 3858 bytes ui/opensnitch/res/icon-white.png | Bin 0 -> 20760 bytes ui/opensnitch/res/icon-white.svg | 80 + ui/opensnitch/res/icon.png | Bin 0 -> 7780 bytes ui/opensnitch/res/preferences.ui | 1406 ++++++++++ ui/opensnitch/res/process_details.ui | 269 ++ ui/opensnitch/res/prompt.ui | 866 ++++++ ui/opensnitch/res/resources.qrc | 8 + ui/opensnitch/res/ruleseditor.ui | 946 +++++++ ui/opensnitch/res/stats.ui | 1652 +++++++++++ ui/opensnitch/service.py | 738 +++++ ui/opensnitch/utils.py | 293 ++ ui/opensnitch/version.py | 1 + ui/requirements.txt | 5 + ui/resources/icons/48x48/opensnitch-ui.png | Bin 0 -> 1834 bytes ui/resources/icons/64x64/opensnitch-ui.png | Bin 0 -> 2380 bytes ui/resources/icons/opensnitch-ui.svg | 95 + ...o.github.evilsocket.opensnitch.appdata.xml | 56 + ui/resources/kcm_opensnitch.desktop | 9 + ui/resources/opensnitch_ui.desktop | 18 + ui/setup.py | 38 + ui/tests/README.md | 23 + ui/tests/__init__.py | 0 ui/tests/dialogs/__init__.py | 52 + ui/tests/dialogs/test_preferences.py | 142 + ui/tests/dialogs/test_ruleseditor.py | 384 +++ ui/tests/test_nodes.py | 149 + utils/legacy/make_ads_rules.py | 69 + utils/scripts/ads/update_adlists.sh | 91 + 193 files changed, 54098 insertions(+) create mode 100644 .github/FUNDING.yml create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature-request.md create mode 100644 .github/workflows/debian-package.yml create mode 100644 .github/workflows/ebpf.yml create mode 100644 .github/workflows/go.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 daemon/.gitignore create mode 100644 daemon/Gopkg.toml create mode 100644 daemon/Makefile create mode 100644 daemon/conman/connection.go create mode 100644 daemon/conman/connection_test.go create mode 100644 daemon/core/core.go create mode 100644 daemon/core/system.go create mode 100644 daemon/core/version.go create mode 100644 daemon/default-config.json create mode 100644 daemon/dns/parse.go create mode 100644 daemon/dns/track.go create mode 100644 daemon/firewall/common/common.go create mode 100644 daemon/firewall/config/config.go create mode 100644 daemon/firewall/iptables/iptables.go create mode 100644 daemon/firewall/iptables/monitor.go create mode 100644 daemon/firewall/iptables/rules.go create mode 100644 daemon/firewall/iptables/system.go create mode 100644 daemon/firewall/nftables/monitor.go create mode 100644 daemon/firewall/nftables/nftables.go create mode 100644 daemon/firewall/nftables/rules.go create mode 100644 daemon/firewall/nftables/system.go create mode 100644 daemon/firewall/rules.go create mode 100644 daemon/go.mod create mode 100644 daemon/log/log.go create mode 100644 daemon/main.go create mode 100644 daemon/netfilter/packet.go create mode 100644 daemon/netfilter/queue.c create mode 100644 daemon/netfilter/queue.go create mode 100644 daemon/netfilter/queue.h create mode 100644 daemon/netlink/socket.go create mode 100644 daemon/netlink/socket_linux.go create mode 100644 daemon/netlink/socket_test.go create mode 100644 daemon/netstat/entry.go create mode 100644 daemon/netstat/find.go create mode 100644 daemon/netstat/parse.go create mode 100644 daemon/opensnitch.spec create mode 100644 daemon/opensnitchd.service create mode 100644 daemon/procmon/activepids.go create mode 100644 daemon/procmon/activepids_test.go create mode 100644 daemon/procmon/audit/client.go create mode 100644 daemon/procmon/audit/parse.go create mode 100644 daemon/procmon/cache.go create mode 100644 daemon/procmon/cache_test.go create mode 100644 daemon/procmon/details.go create mode 100644 daemon/procmon/ebpf/cache.go create mode 100644 daemon/procmon/ebpf/debug.go create mode 100644 daemon/procmon/ebpf/ebpf.go create mode 100644 daemon/procmon/ebpf/find.go create mode 100644 daemon/procmon/ebpf/monitor.go create mode 100644 daemon/procmon/ebpf/utils.go create mode 100644 daemon/procmon/find.go create mode 100644 daemon/procmon/find_test.go create mode 100644 daemon/procmon/monitor/init.go create mode 100644 daemon/procmon/parse.go create mode 100644 daemon/procmon/process.go create mode 100644 daemon/procmon/process_test.go create mode 100644 daemon/rule/loader.go create mode 100644 daemon/rule/loader_test.go create mode 100644 daemon/rule/operator.go create mode 100644 daemon/rule/operator_lists.go create mode 100644 daemon/rule/operator_test.go create mode 100644 daemon/rule/rule.go create mode 100644 daemon/rule/rule_test.go create mode 100644 daemon/rule/testdata/000-allow-chrome.json create mode 100644 daemon/rule/testdata/001-deny-chrome.json create mode 100644 daemon/rule/testdata/invalid-regexp-list.json create mode 100644 daemon/rule/testdata/invalid-regexp.json create mode 100644 daemon/rule/testdata/lists/domains/domainlists.txt create mode 100644 daemon/rule/testdata/lists/ips/ips.txt create mode 100644 daemon/rule/testdata/lists/nets/nets.txt create mode 100644 daemon/rule/testdata/lists/regexp/domainsregexp.txt create mode 100644 daemon/rule/testdata/live_reload/test-live-reload-delete.json create mode 100644 daemon/rule/testdata/live_reload/test-live-reload-remove.json create mode 100644 daemon/statistics/event.go create mode 100644 daemon/statistics/stats.go create mode 100644 daemon/system-fw.json create mode 100644 daemon/ui/client.go create mode 100644 daemon/ui/config.go create mode 100644 daemon/ui/notifications.go create mode 100644 daemon/ui/protocol/.gitkeep create mode 100644 debian/changelog create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/gbp.conf create mode 100644 debian/gitlab-ci.yml create mode 100644 debian/opensnitch.init create mode 100644 debian/opensnitch.install create mode 100644 debian/opensnitch.logrotate create mode 100644 debian/opensnitch.service create mode 100755 debian/python3-opensnitch-ui.postinst create mode 100755 debian/python3-opensnitch-ui.postrm create mode 100755 debian/rules create mode 100644 debian/source/format create mode 100644 debian/source/options create mode 100644 debian/tests/control create mode 100755 debian/tests/test-resources.sh create mode 100644 debian/upstream/metadata create mode 100644 debian/watch create mode 100644 ebpf_prog/Makefile create mode 100644 ebpf_prog/README create mode 100644 ebpf_prog/arm-clang-asm-fix.patch create mode 100644 ebpf_prog/file.patch create mode 100644 ebpf_prog/opensnitch.c create mode 100644 proto/.gitignore create mode 100644 proto/Makefile create mode 100644 proto/ui.proto create mode 100755 release.sh create mode 100644 screenshots/opensnitch-ui-general-tab-deny.png create mode 100644 screenshots/opensnitch-ui-proc-details.png create mode 100644 screenshots/screenshot.png create mode 100644 ui/.gitignore create mode 100644 ui/LICENSE create mode 100644 ui/MANIFEST.in create mode 100644 ui/Makefile create mode 100755 ui/bin/opensnitch-ui create mode 100644 ui/i18n/Makefile create mode 100644 ui/i18n/README.md create mode 100755 ui/i18n/generate_i18n.sh create mode 100644 ui/i18n/locales/de_DE/opensnitch-de_DE.ts create mode 100644 ui/i18n/locales/es_ES/opensnitch-es_ES.ts create mode 100644 ui/i18n/locales/eu_ES/opensnitch-eu_ES.ts create mode 100644 ui/i18n/locales/fr_FR/opensnitch-fr_FR.ts create mode 100644 ui/i18n/locales/hu_HU/opensnitch-hu_HU.ts create mode 100644 ui/i18n/locales/ja_JP/opensnitch-ja_JP.ts create mode 100644 ui/i18n/locales/lt_LT/opensnitch-lt_LT.ts create mode 100644 ui/i18n/locales/nb_NO/opensnitch-nb_NO.ts create mode 100644 ui/i18n/locales/pt_BR/opensnitch-pt_BR.ts create mode 100644 ui/i18n/locales/ro_RO/opensnitch-ro_RO.ts create mode 100644 ui/i18n/locales/ru_RU/opensnitch-ru_RU.ts create mode 100644 ui/i18n/locales/tr_TR/opensnitch-tr_TR.ts create mode 100644 ui/i18n/opensnitch_i18n.pro create mode 100644 ui/opensnitch-ui.spec create mode 100644 ui/opensnitch/__init__.py create mode 100644 ui/opensnitch/config.py create mode 100644 ui/opensnitch/customwidgets/__init__.py create mode 100644 ui/opensnitch/customwidgets/addresstablemodel.py create mode 100644 ui/opensnitch/customwidgets/generictableview.py create mode 100644 ui/opensnitch/customwidgets/main.py create mode 100644 ui/opensnitch/database.py create mode 100644 ui/opensnitch/desktop_parser.py create mode 100644 ui/opensnitch/dialogs/__init__.py create mode 100644 ui/opensnitch/dialogs/preferences.py create mode 100644 ui/opensnitch/dialogs/processdetails.py create mode 100644 ui/opensnitch/dialogs/prompt.py create mode 100644 ui/opensnitch/dialogs/ruleseditor.py create mode 100644 ui/opensnitch/dialogs/stats.py create mode 100644 ui/opensnitch/nodes.py create mode 100644 ui/opensnitch/notifications.py create mode 100644 ui/opensnitch/res/__init__.py create mode 100644 ui/opensnitch/res/icon-alert.png create mode 100644 ui/opensnitch/res/icon-off.png create mode 100644 ui/opensnitch/res/icon-pause.png create mode 100644 ui/opensnitch/res/icon-pause.svg create mode 100644 ui/opensnitch/res/icon-red.png create mode 100644 ui/opensnitch/res/icon-white.png create mode 100644 ui/opensnitch/res/icon-white.svg create mode 100644 ui/opensnitch/res/icon.png create mode 100644 ui/opensnitch/res/preferences.ui create mode 100644 ui/opensnitch/res/process_details.ui create mode 100644 ui/opensnitch/res/prompt.ui create mode 100644 ui/opensnitch/res/resources.qrc create mode 100644 ui/opensnitch/res/ruleseditor.ui create mode 100644 ui/opensnitch/res/stats.ui create mode 100644 ui/opensnitch/service.py create mode 100644 ui/opensnitch/utils.py create mode 100644 ui/opensnitch/version.py create mode 100644 ui/requirements.txt create mode 100644 ui/resources/icons/48x48/opensnitch-ui.png create mode 100644 ui/resources/icons/64x64/opensnitch-ui.png create mode 100644 ui/resources/icons/opensnitch-ui.svg create mode 100644 ui/resources/io.github.evilsocket.opensnitch.appdata.xml create mode 100644 ui/resources/kcm_opensnitch.desktop create mode 100644 ui/resources/opensnitch_ui.desktop create mode 100644 ui/setup.py create mode 100644 ui/tests/README.md create mode 100644 ui/tests/__init__.py create mode 100644 ui/tests/dialogs/__init__.py create mode 100644 ui/tests/dialogs/test_preferences.py create mode 100644 ui/tests/dialogs/test_ruleseditor.py create mode 100644 ui/tests/test_nodes.py create mode 100644 utils/legacy/make_ads_rules.py create mode 100755 utils/scripts/ads/update_adlists.sh diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..f8e81cc --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: evilsocket +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..83149d1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,52 @@ +--- +name: 🐞 Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +Please, check the FAQ and Known Problems pages before creating the bug report: +https://github.com/evilsocket/opensnitch/wiki/FAQs +https://github.com/evilsocket/opensnitch/wiki/Known-problems + +**Describe the bug** +A clear and concise description of what the bug is. + +Include the following information: + - OpenSnitch version. + - OS: [e.g. Debian GNU/Linux, ArchLinux, Slackware, ...] + - Version [e.g. Buster, 10.3, 20.04] + - Window Manager: [e.g. GNOME Shell, KDE, enlightenment, i3wm, ...] + - Kernel version: echo $(uname -a) + +**To Reproduce** +Describe in detail as much as you can what happened. + +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Post error logs:** +If it's a crash of the GUI: + - Launch it from a terminal and reproduce the issue. + - Post the errors logged to the terminal. + +If the daemon doesn't start: + - Post last 15 lines of the log file `/var/log/opensnitchd.log` + - Or launch it from a terminal as root (`# /usr/bin/opensnitchd -rules-path /etc/opensnitchd/rules`) and post the errors logged to the terminal. + +If the deb or rpm packages fail to install: + - Install them from a terminal (`$ sudo dpkg -i opensnitch*` / `$ sudo yum install opensnitch*`), and post the errors logged to stdout. + +**Expected behavior (optional)** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. It may help to understand the issue much better. + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..dd3c313 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,4 @@ +contact_links: + - name: 🙋 Question + url: https://github.com/evilsocket/opensnitch/discussions/new + about: Ask your question here diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 0000000..7e54dce --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,15 @@ +--- +name: 💡 Feature request +about: Suggest an idea +title: '[Feature Request] ' +labels: feature +assignees: '' + +--- + +<!-- +Note: Please, use the search box to see if this feature has already been requested. +--> + +### Summary: +<!-- A concise description of the new feature. --> diff --git a/.github/workflows/debian-package.yml b/.github/workflows/debian-package.yml new file mode 100644 index 0000000..afd2a36 --- /dev/null +++ b/.github/workflows/debian-package.yml @@ -0,0 +1,57 @@ +name: Build status +on: [push, pull_request] +jobs: + + Builddeb: + runs-on: ubuntu-latest + strategy: + matrix: + image: ["debian:bookworm", "debian:sid"] + container: + image: ${{ matrix.image }} + options: --cpus=2 + steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" + + - name: Check out git code + uses: actions/checkout@v2 + + - name: Install pre-dependencies + env: + DEBIAN_FRONTEND: noninteractive + run: | + set -e + set -x + apt --quiet update + # Install stuff needed to check out the linuxcnc repo and turn it into a debian source package. + apt --yes --quiet install --no-install-suggests eatmydata + eatmydata apt --yes --quiet install --no-install-suggests git devscripts + + - name: Install build dependencies + env: + DEBIAN_FRONTEND: noninteractive + run: | + set -e + set -x + eatmydata apt --yes --quiet build-dep --indep-only . + + - name: Build source client + env: + DEBIAN_FRONTEND: noninteractive + run: | + set -e + set -x + # Workaround for missing source tarball + echo 1.0 > debian/source/format + yes y | eatmydata debuild -us -uc + + - name: Test install debian packages + env: + DEBIAN_FRONTEND: noninteractive + run: | + set -e + set -x + eatmydata apt --yes --quiet install ../*.deb diff --git a/.github/workflows/ebpf.yml b/.github/workflows/ebpf.yml new file mode 100644 index 0000000..3ee1bbd --- /dev/null +++ b/.github/workflows/ebpf.yml @@ -0,0 +1,48 @@ +name: Build eBPF +on: + + # Trigger this workflow only when ebpf modules changes. + push: + paths: + - 'ebpf_prog/*' + - '.github/workflows/ebpf.yml' + pull_request: + paths: + - 'ebpf_prog/*' + - '.github/workflows/ebpf.yml' + + # Allow to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + + build: + name: Build eBPF object + runs-on: ubuntu-latest + steps: + + - name: Check out git code + uses: actions/checkout@v2 + + - name: Get and prepare dependencies + run: | + set -e + set -x + sudo apt install eatmydata + sudo eatmydata apt install wget tar patch clang llvm libelf-dev libzip-dev flex bison libssl-dev bc rsync python3 binutils + eatmydata wget --no-verbose https://github.com/torvalds/linux/archive/v5.8.tar.gz + eatmydata tar -xf v5.8.tar.gz + + - name: Build eBPF module + run: | + set -e + set -x + eatmydata patch linux-5.8/tools/lib/bpf/bpf_helpers.h < ebpf_prog/file.patch + eatmydata cp ebpf_prog/opensnitch.c ebpf_prog/Makefile linux-5.8/samples/bpf + cd linux-5.8 && yes "" | eatmydata make oldconfig + eatmydata make prepare + eatmydata make headers_install + cd samples/bpf + eatmydata make + eatmydata objdump -h opensnitch.o + eatmydata llvm-strip -g opensnitch.o diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..09e94d7 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,49 @@ +name: Build status +on: + # Trigger this workflow only when daemon code changes. + push: + paths: + - 'daemon/*' + - '.github/workflows/go.yml' + pull_request: + paths: + - 'daemon/*' + - '.github/workflows/go.yml' + + # Allow to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + + build: + name: Build Go code + runs-on: ubuntu-latest + steps: + + - name: Set up Go 1.15.15 + uses: actions/setup-go@v1 + with: + go-version: 1.15.15 + id: go + + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + + - name: Get dependencies + run: | + sudo apt --yes --quiet install --no-install-suggests eatmydata + sudo eatmydata apt install git libnetfilter-queue-dev libmnl-dev libpcap-dev protobuf-compiler + export GOPATH=~/go + export PATH=$PATH:$GOPATH/bin + eatmydata go get github.com/golang/protobuf/protoc-gen-go + eatmydata go install google.golang.org/protobuf/cmd/protoc-gen-go + eatmydata go get google.golang.org/grpc/cmd/protoc-gen-go-grpc + cd proto + eatmydata make ../daemon/ui/protocol/ui.pb.go + + - name: Build + run: | + cd daemon + eatmydata go mod tidy + eatmydata go mod vendor + eatmydata go build -v . diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2697ff8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.sock +*.pyc +*.profile +rules diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<https://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<https://www.gnu.org/licenses/why-not-lgpl.html>. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ee8fb4a --- /dev/null +++ b/Makefile @@ -0,0 +1,47 @@ +all: protocol opensnitch_daemon gui + +install: + @$(MAKE) -C daemon install + @$(MAKE) -C ui install + +protocol: + @$(MAKE) -C proto + +opensnitch_daemon: + @$(MAKE) -C daemon + +gui: + @$(MAKE) -C ui + +clean: + @$(MAKE) -C daemon clean + @$(MAKE) -C proto clean + @$(MAKE) -C ui clean + +run: + cd ui && pip3 install --upgrade . && cd .. + opensnitch-ui --socket unix:///tmp/osui.sock & + ./daemon/opensnitchd -rules-path /etc/opensnitchd/rules -ui-socket unix:///tmp/osui.sock -cpu-profile cpu.profile -mem-profile mem.profile + +test: + clear + $(MAKE) clean + clear + mkdir -p rules + $(MAKE) + clear + $(MAKE) run + +adblocker: + clear + $(MAKE) clean + clear + $(MAKE) + clear + python make_ads_rules.py + clear + cd ui && pip3 install --upgrade . && cd .. + opensnitch-ui --socket unix:///tmp/osui.sock & + ./daemon/opensnitchd -rules-path /etc/opensnitchd/rules -ui-socket unix:///tmp/osui.sock + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..68c7b15 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +<p align="center"> + <img alt="opensnitch" src="https://raw.githubusercontent.com/evilsocket/opensnitch/master/ui/opensnitch/res/icon.png" height="160" /> + <p align="center"> + <img src="https://github.com/evilsocket/opensnitch/workflows/Build%20status/badge.svg" /> + <a href="https://github.com/evilsocket/opensnitch/releases/latest"><img alt="Release" src="https://img.shields.io/github/release/evilsocket/opensnitch.svg?style=flat-square"></a> + <a href="https://github.com/evilsocket/opensnitch/blob/master/LICENSE.md"><img alt="Software License" src="https://img.shields.io/badge/license-GPL3-brightgreen.svg?style=flat-square"></a> + <a href="https://goreportcard.com/report/github.com/evilsocket/opensnitch/daemon"><img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/evilsocket/opensnitch/daemon?style=flat-square"></a> + <a href="https://repology.org/project/opensnitch/versions"><img src="https://repology.org/badge/tiny-repos/opensnitch.svg" alt="Packaging status"></a> + </p> +</p> + +**OpenSnitch** is a GNU/Linux application firewall. + +<p align="center"> + <img src="https://user-images.githubusercontent.com/2742953/85205382-6ba9cb00-b31b-11ea-8e9a-bd4b8b05a236.png" alt="OpenSnitch"/> +</p> + +### Installation and configuration + +Please, refer to [the documentation](https://github.com/evilsocket/opensnitch/wiki) for detailed information. + +### Contributors + +[See the list](https://github.com/evilsocket/opensnitch/graphs/contributors) diff --git a/daemon/.gitignore b/daemon/.gitignore new file mode 100644 index 0000000..ac08621 --- /dev/null +++ b/daemon/.gitignore @@ -0,0 +1,2 @@ +opensnitchd +vendor diff --git a/daemon/Gopkg.toml b/daemon/Gopkg.toml new file mode 100644 index 0000000..419b318 --- /dev/null +++ b/daemon/Gopkg.toml @@ -0,0 +1,19 @@ +[[constraint]] + name = "github.com/fsnotify/fsnotify" + version = "1.4.7" + +[[constraint]] + name = "github.com/google/gopacket" + version = "~1.1.14" + +[[constraint]] + name = "google.golang.org/grpc" + version = "~1.11.2" + +[[constraint]] + name = "github.com/evilsocket/ftrace" + version = "~1.2.0" + +[prune] + go-tests = true + unused-packages = true diff --git a/daemon/Makefile b/daemon/Makefile new file mode 100644 index 0000000..618e96d --- /dev/null +++ b/daemon/Makefile @@ -0,0 +1,21 @@ +#SRC contains all *.go *.c *.h files in daemon/ and its subfolders +SRC := $(shell find . -type f -name '*.go' -o -name '*.h' -o -name '*.c') + +all: opensnitchd + +install: + @mkdir -p /etc/opensnitchd/rules + @cp opensnitchd /usr/local/bin/ + @cp opensnitchd.service /etc/systemd/system/ + @cp default-config.json /etc/opensnitchd/ + @cp system-fw.json /etc/opensnitchd/ + @systemctl daemon-reload + +opensnitchd: $(SRC) + @go get + @go build -o opensnitchd . + +clean: + @rm -rf opensnitchd + + diff --git a/daemon/conman/connection.go b/daemon/conman/connection.go new file mode 100644 index 0000000..16aad41 --- /dev/null +++ b/daemon/conman/connection.go @@ -0,0 +1,266 @@ +package conman + +import ( + "errors" + "fmt" + "net" + "os" + + "github.com/evilsocket/opensnitch/daemon/core" + "github.com/evilsocket/opensnitch/daemon/dns" + "github.com/evilsocket/opensnitch/daemon/log" + "github.com/evilsocket/opensnitch/daemon/netfilter" + "github.com/evilsocket/opensnitch/daemon/netlink" + "github.com/evilsocket/opensnitch/daemon/netstat" + "github.com/evilsocket/opensnitch/daemon/procmon" + "github.com/evilsocket/opensnitch/daemon/procmon/ebpf" + "github.com/evilsocket/opensnitch/daemon/ui/protocol" + + "github.com/google/gopacket/layers" +) + +// Connection represents an outgoing connection. +type Connection struct { + Protocol string + SrcIP net.IP + SrcPort uint + DstIP net.IP + DstPort uint + DstHost string + Entry *netstat.Entry + Process *procmon.Process + + pkt *netfilter.Packet +} + +var showUnknownCons = false + +// Parse extracts the IP layers from a network packet to determine what +// process generated a connection. +func Parse(nfp netfilter.Packet, interceptUnknown bool) *Connection { + showUnknownCons = interceptUnknown + + if nfp.IsIPv4() { + con, err := NewConnection(&nfp) + if err != nil { + log.Debug("%s", err) + return nil + } else if con == nil { + return nil + } + return con + } + + if core.IPv6Enabled == false { + return nil + } + con, err := NewConnection6(&nfp) + if err != nil { + log.Debug("%s", err) + return nil + } else if con == nil { + return nil + } + return con + +} + +func newConnectionImpl(nfp *netfilter.Packet, c *Connection, protoType string) (cr *Connection, err error) { + // no errors but not enough info neither + if c.parseDirection(protoType) == false { + return nil, nil + } + log.Debug("new connection %s => %d:%v -> %v:%d uid: %d", c.Protocol, c.SrcPort, c.SrcIP, c.DstIP, c.DstPort, nfp.UID) + + c.Entry = &netstat.Entry{ + Proto: c.Protocol, + SrcIP: c.SrcIP, + SrcPort: c.SrcPort, + DstIP: c.DstIP, + DstPort: c.DstPort, + UserId: -1, + INode: -1, + } + + pid := -1 + uid := -1 + if procmon.MethodIsEbpf() { + pid, uid, err = ebpf.GetPid(c.Protocol, c.SrcPort, c.SrcIP, c.DstIP, c.DstPort) + if err != nil { + log.Warning("ebpf warning: %v", err) + return nil, nil + } + } + // sometimes when using eBPF the connection is not found, but falling back to legacy + // methods helps to find it and avoid "unknown/kernel pop-ups". TODO: investigate + if pid < 0 { + // 0. lookup uid and inode via netlink. Can return several inodes. + // 1. lookup uid and inode using /proc/net/(udp|tcp|udplite) + // 2. lookup pid by inode + // 3. if this is coming from us, just accept + // 4. lookup process info by pid + var inodeList []int + uid, inodeList = netlink.GetSocketInfo(c.Protocol, c.SrcIP, c.SrcPort, c.DstIP, c.DstPort) + if len(inodeList) == 0 { + if c.Entry = netstat.FindEntry(c.Protocol, c.SrcIP, c.SrcPort, c.DstIP, c.DstPort); c.Entry == nil { + return nil, fmt.Errorf("Could not find netstat entry for: %s", c) + } + if c.Entry.INode > 0 { + log.Debug("connection found in netstat: %v", c.Entry) + inodeList = append([]int{c.Entry.INode}, inodeList...) + } + } + if len(inodeList) == 0 { + log.Debug("<== no inodes found, applying default action.") + } + + for n, inode := range inodeList { + pid = procmon.GetPIDFromINode(inode, fmt.Sprint(inode, c.SrcIP, c.SrcPort, c.DstIP, c.DstPort)) + if pid != -1 { + log.Debug("[%d] PID found %d [%d]", n, pid, inode) + c.Entry.INode = inode + break + } + } + } + + if nfp.UID != 0xffffffff { + c.Entry.UserId = int(nfp.UID) + } else { + c.Entry.UserId = uid + } + + if pid == os.Getpid() { + // return a Process object with our PID, to be able to exclude our own connections + // (to the UI on a local socket for example) + c.Process = procmon.NewProcess(pid, "") + return c, nil + } + + if c.Process = procmon.FindProcess(pid, showUnknownCons); c.Process == nil { + return nil, fmt.Errorf("Could not find process by its pid %d for: %s", pid, c) + } + + return c, nil +} + +// NewConnection creates a new Connection object, and returns the details of it. +func NewConnection(nfp *netfilter.Packet) (c *Connection, err error) { + ipv4 := nfp.Packet.Layer(layers.LayerTypeIPv4) + if ipv4 == nil { + return nil, errors.New("Error getting IPv4 layer") + } + ip, ok := ipv4.(*layers.IPv4) + if !ok { + return nil, errors.New("Error getting IPv4 layer data") + } + c = &Connection{ + SrcIP: ip.SrcIP, + DstIP: ip.DstIP, + DstHost: dns.HostOr(ip.DstIP, ""), + pkt: nfp, + } + return newConnectionImpl(nfp, c, "") +} + +// NewConnection6 creates a IPv6 new Connection object, and returns the details of it. +func NewConnection6(nfp *netfilter.Packet) (c *Connection, err error) { + ipv6 := nfp.Packet.Layer(layers.LayerTypeIPv6) + if ipv6 == nil { + return nil, errors.New("Error getting IPv6 layer") + } + ip, ok := ipv6.(*layers.IPv6) + if !ok { + return nil, errors.New("Error getting IPv6 layer data") + } + c = &Connection{ + SrcIP: ip.SrcIP, + DstIP: ip.DstIP, + DstHost: dns.HostOr(ip.DstIP, ""), + pkt: nfp, + } + return newConnectionImpl(nfp, c, "6") +} + +func (c *Connection) parseDirection(protoType string) bool { + ret := false + if tcpLayer := c.pkt.Packet.Layer(layers.LayerTypeTCP); tcpLayer != nil { + if tcp, ok := tcpLayer.(*layers.TCP); ok == true && tcp != nil { + c.Protocol = "tcp" + protoType + c.DstPort = uint(tcp.DstPort) + c.SrcPort = uint(tcp.SrcPort) + ret = true + + if tcp.DstPort == 53 { + c.getDomains(c.pkt, c) + } + } + } else if udpLayer := c.pkt.Packet.Layer(layers.LayerTypeUDP); udpLayer != nil { + if udp, ok := udpLayer.(*layers.UDP); ok == true && udp != nil { + c.Protocol = "udp" + protoType + c.DstPort = uint(udp.DstPort) + c.SrcPort = uint(udp.SrcPort) + ret = true + + if udp.DstPort == 53 { + c.getDomains(c.pkt, c) + } + } + } else if udpliteLayer := c.pkt.Packet.Layer(layers.LayerTypeUDPLite); udpliteLayer != nil { + if udplite, ok := udpliteLayer.(*layers.UDPLite); ok == true && udplite != nil { + c.Protocol = "udplite" + protoType + c.DstPort = uint(udplite.DstPort) + c.SrcPort = uint(udplite.SrcPort) + ret = true + } + } + + return ret +} + +func (c *Connection) getDomains(nfp *netfilter.Packet, con *Connection) { + domains := dns.GetQuestions(nfp) + if len(domains) > 0 { + for _, dns := range domains { + con.DstHost = dns + } + } +} + +// To returns the destination host of a connection. +func (c *Connection) To() string { + if c.DstHost == "" { + return c.DstIP.String() + } + return c.DstHost +} + +func (c *Connection) String() string { + if c.Entry == nil { + return fmt.Sprintf("%s ->(%s)-> %s:%d", c.SrcIP, c.Protocol, c.To(), c.DstPort) + } + + if c.Process == nil { + return fmt.Sprintf("%s (uid:%d) ->(%s)-> %s:%d", c.SrcIP, c.Entry.UserId, c.Protocol, c.To(), c.DstPort) + } + + return fmt.Sprintf("%s (%d) -> %s:%d (proto:%s uid:%d)", c.Process.Path, c.Process.ID, c.To(), c.DstPort, c.Protocol, c.Entry.UserId) +} + +// Serialize returns a connection serialized. +func (c *Connection) Serialize() *protocol.Connection { + return &protocol.Connection{ + Protocol: c.Protocol, + SrcIp: c.SrcIP.String(), + SrcPort: uint32(c.SrcPort), + DstIp: c.DstIP.String(), + DstHost: c.DstHost, + DstPort: uint32(c.DstPort), + UserId: uint32(c.Entry.UserId), + ProcessId: uint32(c.Process.ID), + ProcessPath: c.Process.Path, + ProcessArgs: c.Process.Args, + ProcessEnv: c.Process.Env, + ProcessCwd: c.Process.CWD, + } +} diff --git a/daemon/conman/connection_test.go b/daemon/conman/connection_test.go new file mode 100644 index 0000000..6bd4cf6 --- /dev/null +++ b/daemon/conman/connection_test.go @@ -0,0 +1,127 @@ +package conman + +import ( + "fmt" + "net" + "testing" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + + "github.com/evilsocket/opensnitch/daemon/netfilter" +) + +// Adding new packets: +// wireshark -> right click -> Copy as HexDump -> create []byte{} + +func NewTCPPacket() gopacket.Packet { + // 47676:192.168.1.100 -> 1.1.1.1:23 + testTCPPacket := []byte{0x4c, 0x6e, 0x6e, 0xd5, 0x79, 0xbf, 0x00, 0x28, 0x9d, 0x43, 0x7f, 0xd7, 0x08, 0x00, 0x45, 0x10, + 0x00, 0x3c, 0x1d, 0x07, 0x40, 0x00, 0x40, 0x06, 0x59, 0x8e, 0xc0, 0xa8, 0x01, 0x6d, 0x01, 0x01, + 0x01, 0x01, 0xba, 0x3c, 0x00, 0x17, 0x47, 0x7e, 0xf3, 0x0b, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x02, + 0xfa, 0xf0, 0x4c, 0x27, 0x00, 0x00, 0x02, 0x04, 0x05, 0xb4, 0x04, 0x02, 0x08, 0x0a, 0x91, 0xfb, + 0xb5, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x0a} + return gopacket.NewPacket(testTCPPacket, layers.LinkTypeEthernet, gopacket.Default) +} + +func NewUDPPacket() gopacket.Packet { + // 29517:192.168.1.109 -> 1.0.0.1:53 + testUDPPacketDNS := []byte{ + 0x4c, 0x6e, 0x6e, 0xd5, 0x79, 0xbf, 0x00, 0x28, 0x9d, 0x43, 0x7f, 0xd7, 0x08, 0x00, 0x45, 0x00, + 0x00, 0x40, 0x54, 0x1a, 0x40, 0x00, 0x3f, 0x11, 0x24, 0x7d, 0xc0, 0xa8, 0x01, 0x6d, 0x01, 0x00, + 0x00, 0x01, 0x73, 0x4d, 0x00, 0x35, 0x00, 0x2c, 0xf1, 0x17, 0x05, 0x51, 0x00, 0x20, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x70, 0x69, 0x04, 0x68, 0x6f, 0x6c, 0x65, 0x00, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x00, 0x29, 0x10, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + } + + return gopacket.NewPacket(testUDPPacketDNS, layers.LinkTypeEthernet, gopacket.Default) +} + +func EstablishConnection(proto, dst string) (net.Conn, error) { + c, err := net.Dial(proto, dst) + if err != nil { + fmt.Println(err) + return nil, err + } + return c, nil +} + +func ListenOnPort(proto, port string) (net.Listener, error) { + l, err := net.Listen(proto, port) + if err != nil { + fmt.Println(err) + return nil, err + } + return l, nil +} + +func NewPacket(pkt gopacket.Packet) *netfilter.Packet { + return &netfilter.Packet{ + Packet: pkt, + UID: 666, + NetworkProtocol: netfilter.IPv4, + } +} + +func NewDummyConnection(src, dst net.IP) *Connection { + return &Connection{ + SrcIP: src, + DstIP: dst, + } +} + +// Test TCP parseDirection() +func TestParseTCPDirection(t *testing.T) { + srcIP := net.IP{192, 168, 1, 100} + dstIP := net.IP{1, 1, 1, 1} + c := NewDummyConnection(srcIP, dstIP) + // 47676:192.168.1.100 -> 1.1.1.1:23 + pkt := NewPacket(NewTCPPacket()) + c.pkt = pkt + + // parseDirection extracts the src and dst port from a network packet. + if c.parseDirection("") == false { + t.Error("parseDirection() should not be false") + t.Fail() + } + if c.SrcPort != 47676 { + t.Error("parseDirection() SrcPort mismatch:", c) + t.Fail() + } + if c.DstPort != 23 { + t.Error("parseDirection() DstPort mismatch:", c) + t.Fail() + } + if c.Protocol != "tcp" { + t.Error("parseDirection() Protocol mismatch:", c) + t.Fail() + } +} + +// Test UDP parseDirection() +func TestParseUDPDirection(t *testing.T) { + srcIP := net.IP{192, 168, 1, 100} + dstIP := net.IP{1, 0, 0, 1} + c := NewDummyConnection(srcIP, dstIP) + // 29517:192.168.1.109 -> 1.0.0.1:53 + pkt := NewPacket(NewUDPPacket()) + c.pkt = pkt + + // parseDirection extracts the src and dst port from a network packet. + if c.parseDirection("") == false { + t.Error("parseDirection() should not be false") + t.Fail() + } + if c.SrcPort != 29517 { + t.Error("parseDirection() SrcPort mismatch:", c) + t.Fail() + } + if c.DstPort != 53 { + t.Error("parseDirection() DstPort mismatch:", c) + t.Fail() + } + if c.Protocol != "udp" { + t.Error("parseDirection() Protocol mismatch:", c) + t.Fail() + } +} diff --git a/daemon/core/core.go b/daemon/core/core.go new file mode 100644 index 0000000..2d58163 --- /dev/null +++ b/daemon/core/core.go @@ -0,0 +1,68 @@ +package core + +import ( + "fmt" + "os" + "os/exec" + "os/user" + "path/filepath" + "strings" + "time" +) + +const ( + defaultTrimSet = "\r\n\t " +) + +// Trim remove trailing spaces from a string. +func Trim(s string) string { + return strings.Trim(s, defaultTrimSet) +} + +// Exec spawns a new process and reurns the output. +func Exec(executable string, args []string) (string, error) { + path, err := exec.LookPath(executable) + if err != nil { + return "", err + } + + raw, err := exec.Command(path, args...).CombinedOutput() + if err != nil { + return "", err + } + return Trim(string(raw)), nil +} + +// Exists checks if a path exists. +func Exists(path string) bool { + if _, err := os.Stat(path); os.IsNotExist(err) { + return false + } + return true +} + +// ExpandPath replaces '~' shorthand with the user's home directory. +func ExpandPath(path string) (string, error) { + // Check if path is empty + if path != "" { + if strings.HasPrefix(path, "~") { + usr, err := user.Current() + if err != nil { + return "", err + } + // Replace only the first occurrence of ~ + path = strings.Replace(path, "~", usr.HomeDir, 1) + } + return filepath.Abs(path) + } + return "", nil +} + +// GetFileModTime checks if a file has been modified. +func GetFileModTime(filepath string) (time.Time, error) { + fi, err := os.Stat(filepath) + if err != nil || fi.IsDir() { + return time.Now(), fmt.Errorf("GetFileModTime() Invalid file") + } + return fi.ModTime(), nil +} diff --git a/daemon/core/system.go b/daemon/core/system.go new file mode 100644 index 0000000..2bbc93f --- /dev/null +++ b/daemon/core/system.go @@ -0,0 +1,23 @@ +package core + +import ( + "io/ioutil" + "strings" +) + +var ( + // IPv6Enabled indicates if IPv6 protocol is enabled in the system + IPv6Enabled = Exists("/proc/sys/net/ipv6") +) + +// GetHostname returns the name of the host where the daemon is running. +func GetHostname() string { + hostname, _ := ioutil.ReadFile("/proc/sys/kernel/hostname") + return strings.Replace(string(hostname), "\n", "", -1) +} + +// GetKernelVersion returns the name of the host where the daemon is running. +func GetKernelVersion() string { + version, _ := ioutil.ReadFile("/proc/sys/kernel/version") + return strings.Replace(string(version), "\n", "", -1) +} diff --git a/daemon/core/version.go b/daemon/core/version.go new file mode 100644 index 0000000..04ae9e0 --- /dev/null +++ b/daemon/core/version.go @@ -0,0 +1,9 @@ +package core + +// version related consts +const ( + Name = "opensnitch-daemon" + Version = "1.5.8" + Author = "Simone 'evilsocket' Margaritelli" + Website = "https://github.com/evilsocket/opensnitch" +) diff --git a/daemon/default-config.json b/daemon/default-config.json new file mode 100644 index 0000000..d53e4c1 --- /dev/null +++ b/daemon/default-config.json @@ -0,0 +1,17 @@ +{ + "Server": + { + "Address":"unix:///tmp/osui.sock", + "LogFile":"/var/log/opensnitchd.log" + }, + "DefaultAction": "allow", + "DefaultDuration": "once", + "InterceptUnknown": false, + "ProcMonitorMethod": "ebpf", + "LogLevel": 2, + "Firewall": "iptables", + "Stats": { + "MaxEvents": 150, + "MaxStats": 25 + } +} diff --git a/daemon/dns/parse.go b/daemon/dns/parse.go new file mode 100644 index 0000000..971eafe --- /dev/null +++ b/daemon/dns/parse.go @@ -0,0 +1,21 @@ +package dns + +import ( + "github.com/evilsocket/opensnitch/daemon/netfilter" + "github.com/google/gopacket/layers" +) + +// GetQuestions retrieves the domain names a process is trying to resolve. +func GetQuestions(nfp *netfilter.Packet) (questions []string) { + dnsLayer := nfp.Packet.Layer(layers.LayerTypeDNS) + if dnsLayer == nil { + return questions + } + + dns, _ := dnsLayer.(*layers.DNS) + for _, dnsQuestion := range dns.Questions { + questions = append(questions, string(dnsQuestion.Name)) + } + + return questions +} diff --git a/daemon/dns/track.go b/daemon/dns/track.go new file mode 100644 index 0000000..5739b7d --- /dev/null +++ b/daemon/dns/track.go @@ -0,0 +1,99 @@ +package dns + +import ( + "net" + "sync" + + "github.com/evilsocket/opensnitch/daemon/log" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" +) + +var ( + responses = make(map[string]string, 0) + lock = sync.RWMutex{} +) + +// TrackAnswers obtains the resolved domains of a DNS query. +// If the packet is UDP DNS, the domain names are added to the list of resolved domains. +func TrackAnswers(packet gopacket.Packet) bool { + udpLayer := packet.Layer(layers.LayerTypeUDP) + if udpLayer == nil { + return false + } + + udp, ok := udpLayer.(*layers.UDP) + if ok == false || udp == nil { + return false + } + if udp.SrcPort != 53 { + return false + } + + dnsLayer := packet.Layer(layers.LayerTypeDNS) + if dnsLayer == nil { + return false + } + + dnsAns, ok := dnsLayer.(*layers.DNS) + if ok == false || dnsAns == nil { + return false + } + + for _, ans := range dnsAns.Answers { + if ans.Name != nil { + if ans.IP != nil { + Track(ans.IP.String(), string(ans.Name)) + } else if ans.CNAME != nil { + Track(string(ans.CNAME), string(ans.Name)) + } + } + } + + return true +} + +// Track adds a resolved domain to the list. +func Track(resolved string, hostname string) { + lock.Lock() + defer lock.Unlock() + + if resolved == "127.0.0.1" || resolved == "::1" { + return + } + responses[resolved] = hostname + + log.Debug("New DNS record: %s -> %s", resolved, hostname) +} + +// Host returns if a resolved domain is in the list. +func Host(resolved string) (host string, found bool) { + lock.RLock() + defer lock.RUnlock() + + host, found = responses[resolved] + return +} + +// HostOr checks if an IP has a domain name already resolved. +// If the domain is in the list it's returned, otherwise the IP will be returned. +func HostOr(ip net.IP, or string) string { + if host, found := Host(ip.String()); found == true { + // host might have been CNAME; go back until we reach the "root" + seen := make(map[string]bool) // prevent possibility of loops + for { + orig, had := Host(host) + if seen[orig] { + break + } + if !had { + break + } + seen[orig] = true + host = orig + } + return host + } + return or +} diff --git a/daemon/firewall/common/common.go b/daemon/firewall/common/common.go new file mode 100644 index 0000000..d1f2eab --- /dev/null +++ b/daemon/firewall/common/common.go @@ -0,0 +1,102 @@ +package common + +import ( + "sync" + "time" + + "github.com/evilsocket/opensnitch/daemon/log" +) + +type ( + callback func() + callbackBool func() bool + + stopChecker struct { + sync.RWMutex + ch chan bool + } + + // Common holds common fields and functionality of both firewalls, + // iptables and nftables. + Common struct { + sync.RWMutex + QueueNum uint16 + Running bool + RulesChecker *time.Ticker + stopCheckerChan *stopChecker + } +) + +func (s *stopChecker) exit() chan bool { + s.RLock() + defer s.RUnlock() + return s.ch +} + +func (s *stopChecker) stop() { + s.Lock() + defer s.Unlock() + + if s.ch != nil { + s.ch <- true + close(s.ch) + s.ch = nil + } +} + +// SetQueueNum sets the queue number used by the firewall. +// It's the queue where all intercepted connections will be sent. +func (c *Common) SetQueueNum(qNum *int) { + c.Lock() + defer c.Unlock() + + if qNum != nil { + c.QueueNum = uint16(*qNum) + } + +} + +// IsRunning returns if the firewall is running or not. +func (c *Common) IsRunning() bool { + c.RLock() + defer c.RUnlock() + + return c != nil && c.Running +} + +// NewRulesChecker starts monitoring firewall for configuration or rules changes. +func (c *Common) NewRulesChecker(areRulesLoaded callbackBool, reloadRules callback) { + c.Lock() + defer c.Unlock() + + c.stopCheckerChan = &stopChecker{ch: make(chan bool, 1)} + c.RulesChecker = time.NewTicker(time.Second * 30) + + go c.startCheckingRules(areRulesLoaded, reloadRules) +} + +// StartCheckingRules monitors if our rules are loaded. +// If the rules to intercept traffic are not loaded, we'll try to insert them again. +func (c *Common) startCheckingRules(areRulesLoaded callbackBool, reloadRules callback) { + for { + select { + case <-c.stopCheckerChan.exit(): + goto Exit + case <-c.RulesChecker.C: + if areRulesLoaded() == false { + reloadRules() + } + } + } + +Exit: + log.Info("exit checking iptables rules") +} + +// StopCheckingRules stops checking if firewall rules are loaded. +func (c *Common) StopCheckingRules() { + if c.RulesChecker != nil { + c.RulesChecker.Stop() + } + c.stopCheckerChan.stop() +} diff --git a/daemon/firewall/config/config.go b/daemon/firewall/config/config.go new file mode 100644 index 0000000..5345dcc --- /dev/null +++ b/daemon/firewall/config/config.go @@ -0,0 +1,199 @@ +// Package config provides functionality to load and monitor the system +// firewall rules. +// It's inherited by the different firewall packages (iptables, nftables). +// +// The firewall rules defined by the user are reloaded in these cases: +// - When the file system-fw.json changes. +// - When the firewall rules are not present when listing them. +// +package config + +import ( + "encoding/json" + "io/ioutil" + "sync" + + "github.com/evilsocket/opensnitch/daemon/log" + "github.com/fsnotify/fsnotify" +) + +type callback func() + +// FwRule holds the fields of a rule +type FwRule struct { + sync.RWMutex + + Description string + Table string + Chain string + Parameters string + Target string + TargetParameters string +} + +type rulesList struct { + sync.RWMutex + + Rule *FwRule +} + +// SystemConfig holds the list of rules to be added to the system +type SystemConfig struct { + sync.RWMutex + SystemRules []*rulesList +} + +// Config holds the functionality to re/load the firewall configuration from disk. +// This is the configuration to manage the system firewall (iptables, nftables). +type Config struct { + sync.Mutex + + file string + watcher *fsnotify.Watcher + monitorExitChan chan bool + SysConfig SystemConfig + + // subscribe to this channel to receive config reload events + ReloadConfChan chan bool + + // preloadCallback is called before reloading the configuration, + // in order to delete old fw rules. + preloadCallback callback +} + +// NewSystemFwConfig initializes config fields +func (c *Config) NewSystemFwConfig(preLoadCb callback) (*Config, error) { + var err error + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Warning("Error creating firewall config watcher: %s", err) + return nil, err + } + + c.Lock() + defer c.Unlock() + + c.file = "/etc/opensnitchd/system-fw.json" + c.monitorExitChan = make(chan bool, 1) + c.preloadCallback = preLoadCb + c.watcher = watcher + c.ReloadConfChan = make(chan bool, 1) + return c, nil +} + +// LoadDiskConfiguration reads and loads the firewall configuration from disk +func (c *Config) LoadDiskConfiguration(reload bool) { + c.Lock() + defer c.Unlock() + + raw, err := ioutil.ReadFile(c.file) + if err != nil { + log.Error("Error reading firewall configuration from disk %s: %s", c.file, err) + return + } + + c.loadConfiguration(raw) + // we need to monitor the configuration file for changes, regardless if it's + // malformed or not. + c.watcher.Remove(c.file) + if err := c.watcher.Add(c.file); err != nil { + log.Error("Could not watch firewall configuration: %s", err) + return + } + + if reload { + c.ReloadConfChan <- true + return + } + + go c.monitorConfigWorker() +} + +// loadConfigutation reads the system firewall rules from disk. +// Then the rules are added based on the configuration defined. +func (c *Config) loadConfiguration(rawConfig []byte) { + c.SysConfig.Lock() + defer c.SysConfig.Unlock() + + // delete old system rules, that may be different from the new ones + c.preloadCallback() + + if err := json.Unmarshal(rawConfig, &c.SysConfig); err != nil { + // we only log the parser error, giving the user a chance to write a valid config + log.Error("Error parsing firewall configuration %s: %s", c.file, err) + } + log.Info("fw configuration loaded") +} + +func (c *Config) saveConfiguration(rawConfig string) error { + conf, err := json.Marshal([]byte(rawConfig)) + if err != nil { + log.Error("saving json firewall configuration: %s %s", err, conf) + return err + } + + c.loadConfiguration([]byte(rawConfig)) + + if err = ioutil.WriteFile(c.file, []byte(rawConfig), 0644); err != nil { + log.Error("writing firewall configuration to disk: %s", err) + return err + } + return nil +} + +// StopConfigWatcher stops the configuration watcher and stops the subroutine. +func (c *Config) StopConfigWatcher() { + c.Lock() + defer c.Unlock() + + if c.monitorExitChan != nil { + c.monitorExitChan <- true + close(c.monitorExitChan) + } + if c.ReloadConfChan != nil { + c.ReloadConfChan <- false // exit + close(c.ReloadConfChan) + } + + if c.watcher != nil { + c.watcher.Remove(c.file) + c.watcher.Close() + } +} + +func (c *Config) monitorConfigWorker() { + for { + select { + case <-c.monitorExitChan: + goto Exit + case event := <-c.watcher.Events: + if (event.Op&fsnotify.Write == fsnotify.Write) || (event.Op&fsnotify.Remove == fsnotify.Remove) { + c.LoadDiskConfiguration(true) + } + } + } +Exit: + log.Debug("stop monitoring firewall config file") + c.Lock() + c.monitorExitChan = nil + c.Unlock() +} + +// MonitorSystemFw waits for configuration reloads. +func (c *Config) MonitorSystemFw(reloadCallback callback) { + for { + select { + case reload := <-c.ReloadConfChan: + if reload { + reloadCallback() + } else { + goto Exit + } + } + } +Exit: + log.Info("iptables, stop monitoring system fw rules") + c.Lock() + c.ReloadConfChan = nil + c.Unlock() +} diff --git a/daemon/firewall/iptables/iptables.go b/daemon/firewall/iptables/iptables.go new file mode 100644 index 0000000..fbef516 --- /dev/null +++ b/daemon/firewall/iptables/iptables.go @@ -0,0 +1,138 @@ +package iptables + +import ( + "os/exec" + "regexp" + "sync" + + "github.com/evilsocket/opensnitch/daemon/firewall/common" + "github.com/evilsocket/opensnitch/daemon/firewall/config" + "github.com/evilsocket/opensnitch/daemon/log" +) + +// Action is the modifier we apply to a rule. +type Action string + +const ( + // Name is the name that identifies this firewall + Name = "iptables" + // SystemRulePrefix prefix added to each system rule + SystemRulePrefix = "opensnitch-filter" +) + +// Actions we apply to the firewall. +const ( + ADD = Action("-A") + INSERT = Action("-I") + DELETE = Action("-D") + FLUSH = Action("-F") + NEWCHAIN = Action("-N") + DELCHAIN = Action("-X") +) + +// SystemChains holds the fw rules defined by the user +type SystemChains struct { + sync.RWMutex + Rules map[string]config.FwRule +} + +// Iptables struct holds the fields of the iptables fw +type Iptables struct { + sync.Mutex + config.Config + common.Common + + bin string + bin6 string + + regexRulesQuery *regexp.Regexp + regexSystemRulesQuery *regexp.Regexp + + chains SystemChains +} + +// Fw initializes a new Iptables object +func Fw() (*Iptables, error) { + if err := IsAvailable(); err != nil { + return nil, err + } + + reRulesQuery, _ := regexp.Compile(`NFQUEUE.*ctstate NEW,RELATED.*NFQUEUE num.*bypass`) + reSystemRulesQuery, _ := regexp.Compile(SystemRulePrefix + ".*") + + ipt := &Iptables{ + bin: "iptables", + bin6: "ip6tables", + regexRulesQuery: reRulesQuery, + regexSystemRulesQuery: reSystemRulesQuery, + chains: SystemChains{Rules: make(map[string]config.FwRule)}, + } + return ipt, nil +} + +// Name returns the firewall name +func (ipt *Iptables) Name() string { + return Name +} + +// Init inserts the firewall rules and starts monitoring for firewall +// changes. +func (ipt *Iptables) Init(qNum *int) { + if ipt.IsRunning() { + return + } + ipt.SetQueueNum(qNum) + + // In order to clean up any existing firewall rule before start, + // we need to load the fw configuration first. + ipt.NewSystemFwConfig(ipt.preloadConfCallback) + go ipt.MonitorSystemFw(ipt.AddSystemRules) + ipt.LoadDiskConfiguration(false) + + // start from a clean state + ipt.CleanRules(false) + ipt.InsertRules() + + ipt.AddSystemRules() + // start monitoring firewall rules to intercept network traffic + ipt.NewRulesChecker(ipt.AreRulesLoaded, ipt.reloadRulesCallback) + + ipt.Running = true +} + +// Stop deletes the firewall rules, allowing network traffic. +func (ipt *Iptables) Stop() { + if ipt.Running == false { + return + } + ipt.StopConfigWatcher() + ipt.StopCheckingRules() + ipt.CleanRules(log.GetLogLevel() == log.DEBUG) + + ipt.Running = false +} + +// IsAvailable checks if iptables is installed in the system. +func IsAvailable() error { + _, err := exec.Command("iptables", []string{"-V"}...).CombinedOutput() + if err != nil { + return err + } + return nil +} + +// InsertRules adds fw rules to intercept connections +func (ipt *Iptables) InsertRules() { + if err4, err6 := ipt.QueueDNSResponses(true, true); err4 != nil || err6 != nil { + log.Error("Error while running DNS firewall rule: %s %s", err4, err6) + } else if err4, err6 = ipt.QueueConnections(true, true); err4 != nil || err6 != nil { + log.Fatal("Error while running conntrack firewall rule: %s %s", err4, err6) + } +} + +// CleanRules deletes the rules we added. +func (ipt *Iptables) CleanRules(logErrors bool) { + ipt.QueueDNSResponses(false, logErrors) + ipt.QueueConnections(false, logErrors) + ipt.DeleteSystemRules(true, logErrors) +} diff --git a/daemon/firewall/iptables/monitor.go b/daemon/firewall/iptables/monitor.go new file mode 100644 index 0000000..316b3b8 --- /dev/null +++ b/daemon/firewall/iptables/monitor.go @@ -0,0 +1,62 @@ +package iptables + +import ( + "github.com/evilsocket/opensnitch/daemon/core" + "github.com/evilsocket/opensnitch/daemon/log" +) + +// AreRulesLoaded checks if the firewall rules for intercept traffic are loaded. +func (ipt *Iptables) AreRulesLoaded() bool { + var outMangle6 string + + outMangle, err := core.Exec("iptables", []string{"-n", "-L", "OUTPUT", "-t", "mangle"}) + if err != nil { + return false + } + + if core.IPv6Enabled { + outMangle6, err = core.Exec("ip6tables", []string{"-n", "-L", "OUTPUT", "-t", "mangle"}) + if err != nil { + return false + } + } + + systemRulesLoaded := true + ipt.chains.RLock() + if len(ipt.chains.Rules) > 0 { + for _, rule := range ipt.chains.Rules { + if chainOut4, err4 := core.Exec("iptables", []string{"-n", "-L", rule.Chain, "-t", rule.Table}); err4 == nil { + if ipt.regexSystemRulesQuery.FindString(chainOut4) == "" { + systemRulesLoaded = false + break + } + } + if core.IPv6Enabled { + if chainOut6, err6 := core.Exec("ip6tables", []string{"-n", "-L", rule.Chain, "-t", rule.Table}); err6 == nil { + if ipt.regexSystemRulesQuery.FindString(chainOut6) == "" { + systemRulesLoaded = false + break + } + } + } + } + } + ipt.chains.RUnlock() + + result := ipt.regexRulesQuery.FindString(outMangle) != "" && + systemRulesLoaded + + if core.IPv6Enabled { + result = result && ipt.regexRulesQuery.FindString(outMangle6) != "" + } + + return result +} + +func (ipt *Iptables) reloadRulesCallback() { + log.Important("firewall rules changed, reloading") + ipt.QueueDNSResponses(false, false) + ipt.QueueConnections(false, false) + ipt.InsertRules() + ipt.AddSystemRules() +} diff --git a/daemon/firewall/iptables/rules.go b/daemon/firewall/iptables/rules.go new file mode 100644 index 0000000..0b32f96 --- /dev/null +++ b/daemon/firewall/iptables/rules.go @@ -0,0 +1,77 @@ +package iptables + +import ( + "fmt" + + "github.com/evilsocket/opensnitch/daemon/core" + "github.com/evilsocket/opensnitch/daemon/log" + "github.com/vishvananda/netlink" +) + +// RunRule inserts or deletes a firewall rule. +func (ipt *Iptables) RunRule(action Action, enable bool, logError bool, rule []string) (err4, err6 error) { + if enable == false { + action = "-D" + } + + rule = append([]string{string(action)}, rule...) + + ipt.Lock() + defer ipt.Unlock() + + if _, err4 = core.Exec(ipt.bin, rule); err4 != nil { + if logError { + log.Error("Error while running firewall rule, ipv4 err: %s", err4) + log.Error("rule: %s", rule) + } + } + + // On some systems IPv6 is disabled + if core.IPv6Enabled { + if _, err6 = core.Exec(ipt.bin6, rule); err6 != nil { + if logError { + log.Error("Error while running firewall rule, ipv6 err: %s", err6) + log.Error("rule: %s", rule) + } + } + } + + return +} + +// QueueDNSResponses redirects DNS responses to us, in order to keep a cache +// of resolved domains. +// INPUT --protocol udp --sport 53 -j NFQUEUE --queue-num 0 --queue-bypass +func (ipt *Iptables) QueueDNSResponses(enable bool, logError bool) (err4, err6 error) { + return ipt.RunRule(INSERT, enable, logError, []string{ + "INPUT", + "--protocol", "udp", + "--sport", "53", + "-j", "NFQUEUE", + "--queue-num", fmt.Sprintf("%d", ipt.QueueNum), + "--queue-bypass", + }) +} + +// QueueConnections inserts the firewall rule which redirects connections to us. +// They are queued until the user denies/accept them, or reaches a timeout. +// OUTPUT -t mangle -m conntrack --ctstate NEW,RELATED -j NFQUEUE --queue-num 0 --queue-bypass +func (ipt *Iptables) QueueConnections(enable bool, logError bool) (error, error) { + err4, err6 := ipt.RunRule(INSERT, enable, logError, []string{ + "OUTPUT", + "-t", "mangle", + "-m", "conntrack", + "--ctstate", "NEW,RELATED", + "-j", "NFQUEUE", + "--queue-num", fmt.Sprintf("%d", ipt.QueueNum), + "--queue-bypass", + }) + if enable { + // flush conntrack as soon as netfilter rule is set. This ensures that already-established + // connections will go to netfilter queue. + if err := netlink.ConntrackTableFlush(netlink.ConntrackTable); err != nil { + log.Error("error in ConntrackTableFlush %s", err) + } + } + return err4, err6 +} diff --git a/daemon/firewall/iptables/system.go b/daemon/firewall/iptables/system.go new file mode 100644 index 0000000..42da0dd --- /dev/null +++ b/daemon/firewall/iptables/system.go @@ -0,0 +1,89 @@ +package iptables + +import ( + "strings" + + "github.com/evilsocket/opensnitch/daemon/firewall/config" + "github.com/evilsocket/opensnitch/daemon/log" +) + +// CreateSystemRule creates the custom firewall chains and adds them to the system. +func (ipt *Iptables) CreateSystemRule(rule *config.FwRule, logErrors bool) { + ipt.chains.Lock() + defer ipt.chains.Unlock() + if rule == nil { + return + } + + chainName := SystemRulePrefix + "-" + rule.Chain + if _, ok := ipt.chains.Rules[rule.Table+"-"+chainName]; ok { + return + } + ipt.RunRule(NEWCHAIN, true, logErrors, []string{chainName, "-t", rule.Table}) + + // Insert the rule at the top of the chain + if err4, err6 := ipt.RunRule(INSERT, true, logErrors, []string{rule.Chain, "-t", rule.Table, "-j", chainName}); err4 == nil && err6 == nil { + ipt.chains.Rules[rule.Table+"-"+chainName] = *rule + } +} + +// DeleteSystemRules deletes the system rules. +// If force is false and the rule has not been previously added, +// it won't try to delete the rules. Otherwise it'll try to delete them. +func (ipt *Iptables) DeleteSystemRules(force, logErrors bool) { + ipt.chains.Lock() + defer ipt.chains.Unlock() + + for _, r := range ipt.SysConfig.SystemRules { + if r.Rule == nil { + continue + } + chain := SystemRulePrefix + "-" + r.Rule.Chain + if _, ok := ipt.chains.Rules[r.Rule.Table+"-"+chain]; !ok && !force { + continue + } + ipt.RunRule(FLUSH, true, false, []string{chain, "-t", r.Rule.Table}) + ipt.RunRule(DELETE, false, logErrors, []string{r.Rule.Chain, "-t", r.Rule.Table, "-j", chain}) + ipt.RunRule(DELCHAIN, true, false, []string{chain, "-t", r.Rule.Table}) + delete(ipt.chains.Rules, r.Rule.Table+"-"+chain) + } +} + +// AddSystemRule inserts a new rule. +func (ipt *Iptables) AddSystemRule(rule *config.FwRule, enable bool) (err4, err6 error) { + if rule == nil { + return nil, nil + } + rule.RLock() + defer rule.RUnlock() + + chain := SystemRulePrefix + "-" + rule.Chain + if rule.Table == "" { + rule.Table = "filter" + } + r := []string{chain, "-t", rule.Table} + if rule.Parameters != "" { + r = append(r, strings.Split(rule.Parameters, " ")...) + } + r = append(r, []string{"-j", rule.Target}...) + if rule.TargetParameters != "" { + r = append(r, strings.Split(rule.TargetParameters, " ")...) + } + + return ipt.RunRule(ADD, enable, true, r) +} + +// AddSystemRules creates the system firewall from configuration. +func (ipt *Iptables) AddSystemRules() { + ipt.DeleteSystemRules(true, false) + + for _, r := range ipt.SysConfig.SystemRules { + ipt.CreateSystemRule(r.Rule, true) + ipt.AddSystemRule(r.Rule, true) + } +} + +// preloadConfCallback gets called before the fw configuration is reloaded +func (ipt *Iptables) preloadConfCallback() { + ipt.DeleteSystemRules(true, log.GetLogLevel() == log.DEBUG) +} diff --git a/daemon/firewall/nftables/monitor.go b/daemon/firewall/nftables/monitor.go new file mode 100644 index 0000000..9e6621f --- /dev/null +++ b/daemon/firewall/nftables/monitor.go @@ -0,0 +1,55 @@ +package nftables + +import ( + "github.com/evilsocket/opensnitch/daemon/log" +) + +// AreRulesLoaded checks if the firewall rules for intercept traffic are loaded. +func (n *Nft) AreRulesLoaded() bool { + n.Lock() + defer n.Unlock() + + nRules := 0 + for _, table := range n.mangleTables { + rules, err := n.conn.GetRule(table, n.outputChains[table]) + if err != nil { + log.Error("nftables mangle rules error: %s, %s", table.Name, n.outputChains[table].Name) + return false + } + for _, r := range rules { + if string(r.UserData) == fwKey { + nRules++ + } + } + } + if nRules != 2 { + log.Warning("nftables mangle rules not loaded: %d", nRules) + return false + } + + nRules = 0 + for _, table := range n.filterTables { + rules, err := n.conn.GetRule(table, n.inputChains[table]) + if err != nil { + log.Error("nftables filter rules error: %s, %s", table.Name, n.inputChains[table].Name) + return false + } + for _, r := range rules { + if string(r.UserData) == fwKey { + nRules++ + } + } + } + if nRules != 2 { + log.Warning("nfables filter rules not loaded: %d", nRules) + return false + } + + return true +} + +func (n *Nft) reloadRulesCallback() { + log.Important("nftables firewall rules changed, reloading") + n.AddSystemRules() + n.InsertRules() +} diff --git a/daemon/firewall/nftables/nftables.go b/daemon/firewall/nftables/nftables.go new file mode 100644 index 0000000..65975da --- /dev/null +++ b/daemon/firewall/nftables/nftables.go @@ -0,0 +1,141 @@ +package nftables + +import ( + "sync" + + "github.com/evilsocket/opensnitch/daemon/firewall/common" + "github.com/evilsocket/opensnitch/daemon/firewall/config" + "github.com/evilsocket/opensnitch/daemon/firewall/iptables" + "github.com/evilsocket/opensnitch/daemon/log" + "github.com/google/nftables" +) + +const ( + // Name is the name that identifies this firewall + Name = "nftables" + + mangleTableName = "mangle" + filterTableName = "filter" + // The following chains will be under our own mangle or filter tables. + // There shouldn't be other chains with the same name here. + outputChain = "output" + inputChain = "input" + // key assigned to every fw rule we add, in order to get rules by this key. + fwKey = "opensnitch-key" +) + +var ( + filterTable = &nftables.Table{ + Family: nftables.TableFamilyIPv4, + Name: filterTableName, + } + filterTable6 = &nftables.Table{ + Family: nftables.TableFamilyIPv6, + Name: filterTableName, + } + mangleTable = &nftables.Table{ + Family: nftables.TableFamilyIPv4, + Name: mangleTableName, + } + mangleTable6 = &nftables.Table{ + Family: nftables.TableFamilyIPv6, + Name: mangleTableName, + } +) + +// Nft holds the fields of our nftables firewall +type Nft struct { + sync.Mutex + config.Config + common.Common + + conn *nftables.Conn + + mangleTables []*nftables.Table + filterTables []*nftables.Table + outputChains map[*nftables.Table]*nftables.Chain + inputChains map[*nftables.Table]*nftables.Chain + + chains iptables.SystemChains +} + +// NewNft creates a new nftables object +func NewNft() *nftables.Conn { + return &nftables.Conn{} +} + +// Fw initializes a new nftables object +func Fw() (*Nft, error) { + n := &Nft{ + outputChains: make(map[*nftables.Table]*nftables.Chain), + inputChains: make(map[*nftables.Table]*nftables.Chain), + chains: iptables.SystemChains{Rules: make(map[string]config.FwRule)}, + } + return n, nil +} + +// Name returns the name of the firewall +func (n *Nft) Name() string { + return Name +} + +// Init inserts the firewall rules and starts monitoring for firewall +// changes. +func (n *Nft) Init(qNum *int) { + if n.IsRunning() { + return + } + n.SetQueueNum(qNum) + n.conn = NewNft() + + // In order to clean up any existing firewall rule before start, + // we need to load the fw configuration first. + n.NewSystemFwConfig(n.preloadConfCallback) + go n.MonitorSystemFw(n.AddSystemRules) + n.LoadDiskConfiguration(false) + + // start from a clean state + n.CleanRules(false) + n.AddSystemRules() + + n.InsertRules() + // start monitoring firewall rules to intercept network traffic. + n.NewRulesChecker(n.AreRulesLoaded, n.reloadRulesCallback) + + n.Running = true +} + +// Stop deletes the firewall rules, allowing network traffic. +func (n *Nft) Stop() { + if n.IsRunning() == false { + return + } + n.StopConfigWatcher() + n.StopCheckingRules() + n.CleanRules(log.GetLogLevel() == log.DEBUG) + + n.Running = false +} + +// InsertRules adds fw rules to intercept connections +func (n *Nft) InsertRules() { + n.delInterceptionRules() + n.addGlobalTables() + n.addGlobalChains() + + if err, _ := n.QueueDNSResponses(true, true); err != nil { + log.Error("Error while Running DNS nftables rule: %s", err) + } else if err, _ = n.QueueConnections(true, true); err != nil { + log.Fatal("Error while Running conntrack nftables rule: %s", err) + } +} + +// CleanRules deletes the rules we added. +func (n *Nft) CleanRules(logErrors bool) { + n.delInterceptionRules() + err := n.conn.Flush() + if err != nil && logErrors { + log.Error("Error cleaning nftables tables: %s", err) + } + n.DeleteSystemRules(true, logErrors) +} diff --git a/daemon/firewall/nftables/rules.go b/daemon/firewall/nftables/rules.go new file mode 100644 index 0000000..8f1a7f6 --- /dev/null +++ b/daemon/firewall/nftables/rules.go @@ -0,0 +1,201 @@ +package nftables + +import ( + "github.com/evilsocket/opensnitch/daemon/log" + "github.com/google/nftables" + "github.com/google/nftables/binaryutil" + "github.com/google/nftables/expr" + "github.com/vishvananda/netlink" + "golang.org/x/sys/unix" +) + +func (n *Nft) addGlobalTables() error { + filter := n.conn.AddTable(filterTable) + filter6 := n.conn.AddTable(filterTable6) + + mangle := n.conn.AddTable(mangleTable) + mangle6 := n.conn.AddTable(mangleTable6) + n.mangleTables = []*nftables.Table{mangle, mangle6} + n.filterTables = []*nftables.Table{filter, filter6} + + // apply changes + if err := n.conn.Flush(); err != nil { + return err + } + + return nil +} + +// TODO: add more parameters, make it more generic +func (n *Nft) addChain(name string, table *nftables.Table, prio *nftables.ChainPriority, ctype nftables.ChainType, hook *nftables.ChainHook) *nftables.Chain { + // nft list chains + return n.conn.AddChain(&nftables.Chain{ + Name: name, + Table: table, + Type: ctype, + Hooknum: hook, + Priority: prio, + //Policy: nftables.ChainPolicyDrop + }) +} + +func (n *Nft) addGlobalChains() error { + // nft list tables + for _, table := range n.mangleTables { + n.outputChains[table] = n.addChain(outputChain, table, nftables.ChainPriorityMangle, nftables.ChainTypeRoute, nftables.ChainHookOutput) + } + for _, table := range n.filterTables { + n.inputChains[table] = n.addChain(inputChain, table, nftables.ChainPriorityFilter, nftables.ChainTypeFilter, nftables.ChainHookInput) + } + // apply changes + if err := n.conn.Flush(); err != nil { + log.Warning("Error adding nftables mangle tables: %v", err) + } + + return nil +} + +// QueueDNSResponses redirects DNS responses to us, in order to keep a cache +// of resolved domains. +// nft insert rule ip filter input udp sport 53 queue num 0 bypass +func (n *Nft) QueueDNSResponses(enable bool, logError bool) (error, error) { + if n.conn == nil { + return nil, nil + } + for _, table := range n.filterTables { + // nft list ruleset -a + n.conn.InsertRule(&nftables.Rule{ + Position: 0, + Table: table, + Chain: n.inputChains[table], + Exprs: []expr.Any{ + &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}, + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: []byte{unix.IPPROTO_UDP}, + }, + &expr.Payload{ + DestRegister: 1, + Base: expr.PayloadBaseTransportHeader, + Offset: 0, + Len: 2, + }, + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: binaryutil.BigEndian.PutUint16(uint16(53)), + }, + &expr.Queue{ + Num: n.QueueNum, + Flag: expr.QueueFlagBypass, + }, + }, + // rule key, to allow get it later by key + UserData: []byte(fwKey), + }) + } + // apply changes + if err := n.conn.Flush(); err != nil { + return err, nil + } + + return nil, nil +} + +// QueueConnections inserts the firewall rule which redirects connections to us. +// They are queued until the user denies/accept them, or reaches a timeout. +// nft insert rule ip mangle OUTPUT ct state new queue num 0 bypass +func (n *Nft) QueueConnections(enable bool, logError bool) (error, error) { + if n.conn == nil { + return nil, nil + } + if enable { + // flush conntrack as soon as netfilter rule is set. This ensures that already-established + // connections will go to netfilter queue. + if err := netlink.ConntrackTableFlush(netlink.ConntrackTable); err != nil { + log.Error("nftables, error in ConntrackTableFlush %s", err) + } + } + + for _, table := range n.mangleTables { + n.conn.InsertRule(&nftables.Rule{ + Position: 0, + Table: table, + Chain: n.outputChains[table], + Exprs: []expr.Any{ + &expr.Ct{Register: 1, SourceRegister: false, Key: expr.CtKeySTATE}, + &expr.Bitwise{ + SourceRegister: 1, + DestRegister: 1, + Len: 4, + Mask: binaryutil.NativeEndian.PutUint32(expr.CtStateBitNEW | expr.CtStateBitRELATED), + Xor: binaryutil.NativeEndian.PutUint32(0), + }, + &expr.Cmp{Op: expr.CmpOpNeq, Register: 1, Data: []byte{0, 0, 0, 0}}, + &expr.Queue{ + Num: n.QueueNum, + Flag: expr.QueueFlagBypass, + }, + }, + // rule key, to allow get it later by key + UserData: []byte(fwKey), + }) + } + // apply changes + if err := n.conn.Flush(); err != nil { + return err, nil + } + + return nil, nil +} + +func (n *Nft) delInterceptionRules() { + n.delRulesByKey(fwKey) +} + +func (n *Nft) delRulesByKey(key string) { + chains, err := n.conn.ListChains() + if err != nil { + log.Warning("nftables, error listing chains: %s", err) + return + } + commit := false + for _, c := range chains { + rules, err := n.conn.GetRule(c.Table, c) + if err != nil { + log.Warning("nftables, error listing rules (%s): %s", c.Table.Name, err) + continue + } + + commit = false + for _, r := range rules { + if string(r.UserData) != key { + continue + } + // just passing the rule object doesn't work. + if err := n.conn.DelRule(&nftables.Rule{ + Table: c.Table, + Chain: c, + Handle: r.Handle, + }); err != nil { + log.Warning("nftables, error adding rule to be deleted (%s/%s): %s", c.Table.Name, c.Name, err) + continue + } + commit = true + } + if commit { + if err := n.conn.Flush(); err != nil { + log.Warning("nftables, error deleting interception rules (%s/%s): %s", c.Table.Name, c.Name, err) + } + } + if rules, err := n.conn.GetRule(c.Table, c); err == nil { + if commit && len(rules) == 0 { + n.conn.DelChain(c) + n.conn.Flush() + } + } + } + + return +} diff --git a/daemon/firewall/nftables/system.go b/daemon/firewall/nftables/system.go new file mode 100644 index 0000000..2d7497c --- /dev/null +++ b/daemon/firewall/nftables/system.go @@ -0,0 +1,40 @@ +package nftables + +import ( + "github.com/evilsocket/opensnitch/daemon/firewall/config" + "github.com/evilsocket/opensnitch/daemon/log" +) + +// CreateSystemRule create the custom firewall chains and adds them to system. +// nft insert rule ip opensnitch-filter opensnitch-input udp dport 1153 +func (n *Nft) CreateSystemRule(rule *config.FwRule, logErrors bool) { + // TODO +} + +// DeleteSystemRules deletes the system rules. +// If force is false and the rule has not been previously added, +// it won't try to delete the rules. Otherwise it'll try to delete them. +func (n *Nft) DeleteSystemRules(force, logErrors bool) { + // TODO +} + +// AddSystemRule inserts a new rule. +func (n *Nft) AddSystemRule(rule *config.FwRule, enable bool) (error, error) { + // TODO + return nil, nil +} + +// AddSystemRules creates the system firewall from configuration +func (n *Nft) AddSystemRules() { + n.DeleteSystemRules(true, false) + + for _, r := range n.SysConfig.SystemRules { + n.CreateSystemRule(r.Rule, true) + n.AddSystemRule(r.Rule, true) + } +} + +// preloadConfCallback gets called before the fw configuration is reloaded +func (n *Nft) preloadConfCallback() { + n.DeleteSystemRules(true, log.GetLogLevel() == log.DEBUG) +} diff --git a/daemon/firewall/rules.go b/daemon/firewall/rules.go new file mode 100644 index 0000000..4b83388 --- /dev/null +++ b/daemon/firewall/rules.go @@ -0,0 +1,85 @@ +package firewall + +import ( + "github.com/evilsocket/opensnitch/daemon/firewall/config" + "github.com/evilsocket/opensnitch/daemon/firewall/iptables" + "github.com/evilsocket/opensnitch/daemon/firewall/nftables" + "github.com/evilsocket/opensnitch/daemon/log" +) + +// Firewall is the interface that all firewalls (iptables, nftables) must implement. +type Firewall interface { + Init(*int) + Stop() + Name() string + IsRunning() bool + SetQueueNum(num *int) + + InsertRules() + QueueDNSResponses(bool, bool) (error, error) + QueueConnections(bool, bool) (error, error) + CleanRules(bool) + + AddSystemRules() + DeleteSystemRules(bool, bool) + AddSystemRule(*config.FwRule, bool) (error, error) + CreateSystemRule(*config.FwRule, bool) +} + +var fw Firewall + +// IsRunning returns if the firewall is running or not. +func IsRunning() bool { + return fw != nil && fw.IsRunning() +} + +// CleanRules deletes the rules we added. +func CleanRules(logErrors bool) { + if fw == nil { + return + } + fw.CleanRules(logErrors) +} + +// Stop deletes the firewall rules, allowing network traffic. +func Stop() { + if fw == nil { + return + } + fw.Stop() +} + +// Init initializes the firewall and loads firewall rules. +func Init(fwType string, qNum *int) { + var err error + + if fwType == iptables.Name { + fw, err = iptables.Fw() + if err != nil { + log.Warning("iptables not available: %s", err) + } + } + + // if iptables is not installed, we can add nftables rules directly to the kernel, + // without relying on any binaries. + if fwType == nftables.Name || err != nil { + fw, err = nftables.Fw() + if err != nil { + log.Warning("nftables not available: %s", err) + } + } + + if err != nil { + log.Warning("firewall error: %s, not iptables nor nftables are available or are usable. Please, report it on github.", err) + return + } + + if fw == nil { + log.Error("firewall not initialized.") + return + } + fw.Stop() + fw.Init(qNum) + + log.Info("Using %s firewall", fw.Name()) +} diff --git a/daemon/go.mod b/daemon/go.mod new file mode 100644 index 0000000..35d5145 --- /dev/null +++ b/daemon/go.mod @@ -0,0 +1,16 @@ +module github.com/evilsocket/opensnitch/daemon + +go 1.15 + +require ( + github.com/fsnotify/fsnotify v1.4.7 + github.com/golang/protobuf v1.5.0 + github.com/google/gopacket v1.1.14 + github.com/google/nftables v0.1.0 + github.com/iovisor/gobpf v0.2.0 + github.com/vishvananda/netlink v0.0.0-20210811191823-e1a867c6b452 + golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271 + golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1 + google.golang.org/grpc v1.32.0 + google.golang.org/protobuf v1.26.0 +) diff --git a/daemon/log/log.go b/daemon/log/log.go new file mode 100644 index 0000000..493f8e5 --- /dev/null +++ b/daemon/log/log.go @@ -0,0 +1,212 @@ +package log + +import ( + "fmt" + "os" + "strings" + "sync" + "time" +) + +type Handler func(format string, args ...interface{}) + +// https://misc.flogisoft.com/bash/tip_colors_and_formatting +const ( + BOLD = "\033[1m" + DIM = "\033[2m" + + RED = "\033[31m" + GREEN = "\033[32m" + BLUE = "\033[34m" + YELLOW = "\033[33m" + + FG_BLACK = "\033[30m" + FG_WHITE = "\033[97m" + + BG_DGRAY = "\033[100m" + BG_RED = "\033[41m" + BG_GREEN = "\033[42m" + BG_YELLOW = "\033[43m" + BG_LBLUE = "\033[104m" + + RESET = "\033[0m" +) + +// log level constants +const ( + DEBUG = iota + INFO + IMPORTANT + WARNING + ERROR + FATAL +) + +// +var ( + WithColors = true + Output = os.Stdout + StdoutFile = "/dev/stdout" + DateFormat = "2006-01-02 15:04:05" + MinLevel = INFO + + mutex = &sync.RWMutex{} + labels = map[int]string{ + DEBUG: "DBG", + INFO: "INF", + IMPORTANT: "IMP", + WARNING: "WAR", + ERROR: "ERR", + FATAL: "!!!", + } + colors = map[int]string{ + DEBUG: DIM + FG_BLACK + BG_DGRAY, + INFO: FG_WHITE + BG_GREEN, + IMPORTANT: FG_WHITE + BG_LBLUE, + WARNING: FG_WHITE + BG_YELLOW, + ERROR: FG_WHITE + BG_RED, + FATAL: FG_WHITE + BG_RED + BOLD, + } +) + +// Wrap wraps a text with effects +func Wrap(s, effect string) string { + if WithColors == true { + s = effect + s + RESET + } + return s +} + +// Dim dims a text +func Dim(s string) string { + return Wrap(s, DIM) +} + +// Bold bolds a text +func Bold(s string) string { + return Wrap(s, BOLD) +} + +// Red reds the text +func Red(s string) string { + return Wrap(s, RED) +} + +// Green greens the text +func Green(s string) string { + return Wrap(s, GREEN) +} + +// Blue blues the text +func Blue(s string) string { + return Wrap(s, BLUE) +} + +// Yellow yellows the text +func Yellow(s string) string { + return Wrap(s, YELLOW) +} + +// Raw prints out a text without colors +func Raw(format string, args ...interface{}) { + mutex.Lock() + defer mutex.Unlock() + fmt.Fprintf(Output, format, args...) +} + +// SetLogLevel sets the log level +func SetLogLevel(newLevel int) { + mutex.Lock() + defer mutex.Unlock() + MinLevel = newLevel +} + +// GetLogLevel returns the current log level configured. +func GetLogLevel() int { + mutex.Lock() + defer mutex.Unlock() + + return MinLevel +} + +// Log prints out a text with the given color and format +func Log(level int, format string, args ...interface{}) { + mutex.Lock() + defer mutex.Unlock() + if level >= MinLevel { + label := labels[level] + color := colors[level] + when := time.Now().UTC().Format(DateFormat) + + what := fmt.Sprintf(format, args...) + if strings.HasSuffix(what, "\n") == false { + what += "\n" + } + + l := Dim("[%s]") + r := Wrap(" %s ", color) + " %s" + + fmt.Fprintf(Output, l+" "+r, when, label, what) + } +} + +func setDefaultLogOutput() { + mutex.Lock() + Output = os.Stdout + mutex.Unlock() +} + +// OpenFile opens a file to print out the logs +func OpenFile(logFile string) (err error) { + if logFile == StdoutFile { + setDefaultLogOutput() + return + } + + if Output, err = os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err != nil { + Error("Error opening log: %s %s", logFile, err) + //fallback to stdout + setDefaultLogOutput() + } + Important("Start writing logs to %s", logFile) + + return err +} + +// Close closes the current output file descriptor +func Close() { + if Output != os.Stdout { + Output.Close() + } +} + +// Debug is the log level for debugging purposes +func Debug(format string, args ...interface{}) { + Log(DEBUG, format, args...) +} + +// Info is the log level for informative messages +func Info(format string, args ...interface{}) { + Log(INFO, format, args...) +} + +// Important is the log level for things that must pay attention +func Important(format string, args ...interface{}) { + Log(IMPORTANT, format, args...) +} + +// Warning is the log level for non-critical errors +func Warning(format string, args ...interface{}) { + Log(WARNING, format, args...) +} + +// Error is the log level for errors that should be corrected +func Error(format string, args ...interface{}) { + Log(ERROR, format, args...) +} + +// Fatal is the log level for errors that must be corrected before continue +func Fatal(format string, args ...interface{}) { + Log(FATAL, format, args...) + os.Exit(1) +} diff --git a/daemon/main.go b/daemon/main.go new file mode 100644 index 0000000..254e65d --- /dev/null +++ b/daemon/main.go @@ -0,0 +1,415 @@ +package main + +import ( + "bytes" + "context" + "flag" + "fmt" + "io/ioutil" + golog "log" + "os" + "os/signal" + "runtime" + "runtime/pprof" + "syscall" + "time" + + "github.com/evilsocket/opensnitch/daemon/conman" + "github.com/evilsocket/opensnitch/daemon/core" + "github.com/evilsocket/opensnitch/daemon/dns" + "github.com/evilsocket/opensnitch/daemon/firewall" + "github.com/evilsocket/opensnitch/daemon/log" + "github.com/evilsocket/opensnitch/daemon/netfilter" + "github.com/evilsocket/opensnitch/daemon/netlink" + "github.com/evilsocket/opensnitch/daemon/procmon/monitor" + "github.com/evilsocket/opensnitch/daemon/rule" + "github.com/evilsocket/opensnitch/daemon/statistics" + "github.com/evilsocket/opensnitch/daemon/ui" +) + +var ( + showVersion = false + procmonMethod = "" + logFile = "" + rulesPath = "rules" + noLiveReload = false + queueNum = 0 + repeatQueueNum int //will be set later to queueNum + 1 + workers = 16 + debug = false + warning = false + important = false + errorlog = false + + uiSocket = "" + uiClient = (*ui.Client)(nil) + + cpuProfile = "" + memProfile = "" + + ctx = (context.Context)(nil) + cancel = (context.CancelFunc)(nil) + err = (error)(nil) + rules = (*rule.Loader)(nil) + stats = (*statistics.Statistics)(nil) + queue = (*netfilter.Queue)(nil) + repeatPktChan = (<-chan netfilter.Packet)(nil) + pktChan = (<-chan netfilter.Packet)(nil) + wrkChan = (chan netfilter.Packet)(nil) + sigChan = (chan os.Signal)(nil) + exitChan = (chan bool)(nil) +) + +func init() { + flag.BoolVar(&showVersion, "version", debug, "Show daemon version of this executable and exit.") + + flag.StringVar(&procmonMethod, "process-monitor-method", procmonMethod, "How to search for processes path. Options: ftrace, audit (experimental), ebpf (experimental), proc (default)") + flag.StringVar(&uiSocket, "ui-socket", uiSocket, "Path the UI gRPC service listener (https://github.com/grpc/grpc/blob/master/doc/naming.md).") + flag.StringVar(&rulesPath, "rules-path", rulesPath, "Path to load JSON rules from.") + flag.IntVar(&queueNum, "queue-num", queueNum, "Netfilter queue number.") + flag.IntVar(&workers, "workers", workers, "Number of concurrent workers.") + flag.BoolVar(&noLiveReload, "no-live-reload", debug, "Disable rules live reloading.") + + flag.StringVar(&logFile, "log-file", logFile, "Write logs to this file instead of the standard output.") + flag.BoolVar(&debug, "debug", debug, "Enable debug level logs.") + flag.BoolVar(&warning, "warning", warning, "Enable warning level logs.") + flag.BoolVar(&important, "important", important, "Enable important level logs.") + flag.BoolVar(&errorlog, "error", errorlog, "Enable error level logs.") + + flag.StringVar(&cpuProfile, "cpu-profile", cpuProfile, "Write CPU profile to this file.") + flag.StringVar(&memProfile, "mem-profile", memProfile, "Write memory profile to this file.") +} + +func overwriteLogging() bool { + return debug || warning || important || errorlog || logFile != "" +} + +func setupLogging() { + golog.SetOutput(ioutil.Discard) + if debug { + log.SetLogLevel(log.DEBUG) + } else if warning { + log.SetLogLevel(log.WARNING) + } else if important { + log.SetLogLevel(log.IMPORTANT) + } else if errorlog { + log.SetLogLevel(log.ERROR) + } else { + log.SetLogLevel(log.INFO) + } + + var logFileToUse string + if logFile == "" { + logFileToUse = log.StdoutFile + } else { + logFileToUse = logFile + } + log.Close() + if err := log.OpenFile(logFileToUse); err != nil { + log.Error("Error opening user defined log: %s %s", logFileToUse, err) + } +} + +func setupSignals() { + sigChan = make(chan os.Signal, 1) + exitChan = make(chan bool, workers+1) + signal.Notify(sigChan, + syscall.SIGHUP, + syscall.SIGINT, + syscall.SIGTERM, + syscall.SIGQUIT) + go func() { + sig := <-sigChan + log.Raw("\n") + log.Important("Got signal: %v", sig) + cancel() + }() +} + +func worker(id int) { + log.Debug("Worker #%d started.", id) + for true { + select { + case <-ctx.Done(): + goto Exit + default: + pkt, ok := <-wrkChan + if !ok { + log.Debug("worker channel closed %d", id) + goto Exit + } + onPacket(pkt) + } + } +Exit: + log.Debug("worker #%d exit", id) +} + +func setupWorkers() { + log.Debug("Starting %d workers ...", workers) + // setup the workers + wrkChan = make(chan netfilter.Packet) + for i := 0; i < workers; i++ { + go worker(i) + } +} + +func doCleanup(queue, repeatQueue *netfilter.Queue) { + log.Info("Cleaning up ...") + firewall.Stop() + monitor.End() + uiClient.Close() + queue.Close() + repeatQueue.Close() + + if cpuProfile != "" { + pprof.StopCPUProfile() + } + + if memProfile != "" { + f, err := os.Create(memProfile) + if err != nil { + fmt.Printf("Could not create memory profile: %s\n", err) + return + } + defer f.Close() + runtime.GC() // get up-to-date statistics + if err := pprof.WriteHeapProfile(f); err != nil { + fmt.Printf("Could not write memory profile: %s\n", err) + } + } +} + +func onPacket(packet netfilter.Packet) { + // DNS response, just parse, track and accept. + if dns.TrackAnswers(packet.Packet) == true { + packet.SetVerdictAndMark(netfilter.NF_ACCEPT, packet.Mark) + stats.OnDNSResponse() + return + } + + // Parse the connection state + con := conman.Parse(packet, uiClient.InterceptUnknown()) + if con == nil { + applyDefaultAction(&packet) + return + } + // accept our own connections + if con.Process.ID == os.Getpid() { + packet.SetVerdict(netfilter.NF_ACCEPT) + return + } + + // search a match in preloaded rules + r := acceptOrDeny(&packet, con) + + stats.OnConnectionEvent(con, r, r == nil) +} + +func applyDefaultAction(packet *netfilter.Packet) { + if uiClient.DefaultAction() == rule.Allow { + packet.SetVerdictAndMark(netfilter.NF_ACCEPT, packet.Mark) + } else { + packet.SetVerdict(netfilter.NF_DROP) + } +} + +func acceptOrDeny(packet *netfilter.Packet, con *conman.Connection) *rule.Rule { + r := rules.FindFirstMatch(con) + if r == nil { + // no rule matched + // Note that as soon as we set a verdict on a packet, the next packet in the netfilter queue + // will begin to be processed even if this function hasn't yet returned + + // send a request to the UI client if + // 1) connected and running and 2) we are not already asking + if uiClient.Connected() == false || uiClient.GetIsAsking() == true { + applyDefaultAction(packet) + log.Debug("UI is not running or busy, connected: %v, running: %v", uiClient.Connected(), uiClient.GetIsAsking()) + return nil + } + + uiClient.SetIsAsking(true) + defer uiClient.SetIsAsking(false) + + // In order not to block packet processing, we send our packet to a different netfilter queue + // and then immediately pull it back out of that queue + packet.SetRequeueVerdict(uint16(repeatQueueNum)) + + var o bool + var pkt netfilter.Packet + // don't wait for the packet longer than 1 sec + select { + case pkt, o = <-repeatPktChan: + if !o { + log.Debug("error while receiving packet from repeatPktChan") + return nil + } + case <-time.After(1 * time.Second): + log.Debug("timed out while receiving packet from repeatPktChan") + return nil + } + + //check if the pulled out packet is the same we put in + if res := bytes.Compare(packet.Packet.Data(), pkt.Packet.Data()); res != 0 { + log.Error("The packet which was requeued has changed abruptly. This should never happen. Please report this incident to the Opensnitch developers. %v %v ", packet, pkt) + return nil + } + packet = &pkt + + r = uiClient.Ask(con) + if r == nil { + log.Error("Invalid rule received, applying default action") + applyDefaultAction(packet) + return nil + } + ok := false + pers := "" + action := string(r.Action) + if r.Action == rule.Allow { + action = log.Green(action) + } else { + action = log.Red(action) + } + + // check if and how the rule needs to be saved + if r.Duration == rule.Always { + pers = "Saved" + // add to the loaded rules and persist on disk + if err := rules.Add(r, true); err != nil { + log.Error("Error while saving rule: %s", err) + } else { + ok = true + } + } else { + pers = "Added" + // add to the rules but do not save to disk + if err := rules.Add(r, false); err != nil { + log.Error("Error while adding rule: %s", err) + } else { + ok = true + } + } + + if ok { + log.Important("%s new rule: %s if %s", pers, action, r.Operator.String()) + } + + } + if packet == nil { + log.Debug("Packet nil after processing rules") + return r + } + + if r.Enabled == false { + applyDefaultAction(packet) + ruleName := log.Green(r.Name) + log.Info("DISABLED (%s) %s %s -> %s:%d (%s)", uiClient.DefaultAction(), log.Bold(log.Green("✔")), log.Bold(con.Process.Path), log.Bold(con.To()), con.DstPort, ruleName) + + } else if r.Action == rule.Allow { + packet.SetVerdictAndMark(netfilter.NF_ACCEPT, packet.Mark) + ruleName := log.Green(r.Name) + if r.Operator.Operand == rule.OpTrue { + ruleName = log.Dim(r.Name) + } + log.Debug("%s %s -> %s:%d (%s)", log.Bold(log.Green("✔")), log.Bold(con.Process.Path), log.Bold(con.To()), con.DstPort, ruleName) + } else { + if r.Action == rule.Reject { + netlink.KillSocket(con.Protocol, con.SrcIP, con.SrcPort, con.DstIP, con.DstPort) + } + packet.SetVerdict(netfilter.NF_DROP) + + log.Debug("%s %s -> %s:%d (%s)", log.Bold(log.Red("✘")), log.Bold(con.Process.Path), log.Bold(con.To()), con.DstPort, log.Red(r.Name)) + } + + return r +} + +func main() { + ctx, cancel = context.WithCancel(context.Background()) + defer cancel() + flag.Parse() + + if showVersion { + fmt.Println(core.Version) + os.Exit(0) + } + + setupLogging() + + if cpuProfile != "" { + if f, err := os.Create(cpuProfile); err != nil { + log.Fatal("%s", err) + } else if err := pprof.StartCPUProfile(f); err != nil { + log.Fatal("%s", err) + } + } + + log.Important("Starting %s v%s", core.Name, core.Version) + + rulesPath, err := core.ExpandPath(rulesPath) + if err != nil { + log.Fatal("%s", err) + } + + setupSignals() + + log.Info("Loading rules from %s ...", rulesPath) + if rules, err = rule.NewLoader(!noLiveReload); err != nil { + log.Fatal("%s", err) + } else if err = rules.Load(rulesPath); err != nil { + log.Fatal("%s", err) + } + stats = statistics.New(rules) + + // prepare the queue + setupWorkers() + queue, err := netfilter.NewQueue(uint16(queueNum)) + if err != nil { + log.Warning("Is opensnitchd already running?") + log.Fatal("Error while creating queue #%d: %s", queueNum, err) + } + pktChan = queue.Packets() + + repeatQueueNum = queueNum + 1 + repeatQueue, rqerr := netfilter.NewQueue(uint16(repeatQueueNum)) + if rqerr != nil { + log.Warning("Is opensnitchd already running?") + log.Fatal("Error while creating queue #%d: %s", repeatQueueNum, rqerr) + } + repeatPktChan = repeatQueue.Packets() + + uiClient = ui.NewClient(uiSocket, stats, rules) + stats.SetConfig(uiClient.GetStatsConfig()) + + // queue is ready, run firewall rules + firewall.Init(uiClient.GetFirewallType(), &queueNum) + + if overwriteLogging() { + setupLogging() + } + // overwrite monitor method from configuration if the user has passed + // the option via command line. + if procmonMethod != "" { + if err := monitor.ReconfigureMonitorMethod(procmonMethod); err != nil { + log.Warning("Unable to set process monitor method via parameter: %v", err) + } + } + + log.Info("Running on netfilter queue #%d ...", queueNum) + for { + select { + case <-ctx.Done(): + goto Exit + case pkt, ok := <-pktChan: + if !ok { + goto Exit + } + wrkChan <- pkt + } + } +Exit: + close(wrkChan) + doCleanup(queue, repeatQueue) + os.Exit(0) +} diff --git a/daemon/netfilter/packet.go b/daemon/netfilter/packet.go new file mode 100644 index 0000000..c515613 --- /dev/null +++ b/daemon/netfilter/packet.go @@ -0,0 +1,57 @@ +package netfilter + +import "C" + +import ( + "github.com/google/gopacket" +) + +// packet consts +const ( + IPv4 = 4 +) + +// Verdict holds the action to perform on a packet (NF_DROP, NF_ACCEPT, etc) +type Verdict C.uint + +type VerdictContainer struct { + Verdict Verdict + Mark uint32 + Packet []byte +} + +// Packet holds the data of a network packet +type Packet struct { + Packet gopacket.Packet + Mark uint32 + verdictChannel chan VerdictContainer + UID uint32 + NetworkProtocol uint8 +} + +// SetVerdict emits a veredict on a packet +func (p *Packet) SetVerdict(v Verdict) { + p.verdictChannel <- VerdictContainer{Verdict: v, Packet: nil, Mark: 0} +} + +// SetVerdictAndMark emits a veredict on a packet and marks it in order to not +// analyze it again. +func (p *Packet) SetVerdictAndMark(v Verdict, mark uint32) { + p.verdictChannel <- VerdictContainer{Verdict: v, Packet: nil, Mark: mark} +} + +func (p *Packet) SetRequeueVerdict(newQueueId uint16) { + v := uint(NF_QUEUE) + q := (uint(newQueueId) << 16) + v = v | q + p.verdictChannel <- VerdictContainer{Verdict: Verdict(v), Packet: nil, Mark: 0} +} + +func (p *Packet) SetVerdictWithPacket(v Verdict, packet []byte) { + p.verdictChannel <- VerdictContainer{Verdict: v, Packet: packet, Mark: 0} +} + +// IsIPv4 returns if the packet is IPv4 +func (p *Packet) IsIPv4() bool { + return p.NetworkProtocol == IPv4 +} diff --git a/daemon/netfilter/queue.c b/daemon/netfilter/queue.c new file mode 100644 index 0000000..f2b7ef6 --- /dev/null +++ b/daemon/netfilter/queue.c @@ -0,0 +1,2 @@ +#include "queue.h" + diff --git a/daemon/netfilter/queue.go b/daemon/netfilter/queue.go new file mode 100644 index 0000000..902d1dd --- /dev/null +++ b/daemon/netfilter/queue.go @@ -0,0 +1,242 @@ +package netfilter + +/* +#cgo pkg-config: libnetfilter_queue +#cgo CFLAGS: -Wall -I/usr/include +#cgo LDFLAGS: -L/usr/lib64/ -ldl + +#include "queue.h" +*/ +import "C" + +import ( + "fmt" + "os" + "sync" + "time" + "unsafe" + + "github.com/evilsocket/opensnitch/daemon/log" + "github.com/google/gopacket" + "github.com/google/gopacket/layers" +) + +const ( + AF_INET = 2 + AF_INET6 = 10 + + NF_DROP Verdict = 0 + NF_ACCEPT Verdict = 1 + NF_STOLEN Verdict = 2 + NF_QUEUE Verdict = 3 + NF_REPEAT Verdict = 4 + NF_STOP Verdict = 5 + + NF_DEFAULT_QUEUE_SIZE uint32 = 4096 + NF_DEFAULT_PACKET_SIZE uint32 = 4096 +) + +var ( + queueIndex = make(map[uint32]*chan Packet, 0) + queueIndexLock = sync.RWMutex{} + exitChan = make(chan bool, 1) + + gopacketDecodeOptions = gopacket.DecodeOptions{Lazy: true, NoCopy: true} +) + +// VerdictContainerC is the struct that contains the mark, action, length and +// payload of a packet. +// It's defined in queue.h, and filled on go_callback() +type VerdictContainerC C.verdictContainer + +// Queue holds the information of a netfilter queue. +// The handles of the connection to the kernel and the created queue. +// A channel where the intercepted packets will be received. +// The ID of the queue. +type Queue struct { + h *C.struct_nfq_handle + qh *C.struct_nfq_q_handle + fd C.int + packets chan Packet + idx uint32 +} + +// NewQueue opens a new netfilter queue to receive packets marked with a mark. +func NewQueue(queueID uint16) (q *Queue, err error) { + q = &Queue{ + idx: uint32(time.Now().UnixNano()), + packets: make(chan Packet), + } + + if err = q.create(queueID); err != nil { + return nil, err + } else if err = q.setup(); err != nil { + return nil, err + } + + go q.run(exitChan) + + return q, nil +} + +func (q *Queue) create(queueID uint16) (err error) { + var ret C.int + + if q.h, err = C.nfq_open(); err != nil { + return fmt.Errorf("Error opening Queue handle: %v", err) + } else if ret, err = C.nfq_unbind_pf(q.h, AF_INET); err != nil || ret < 0 { + return fmt.Errorf("Error unbinding existing q handler from AF_INET protocol family: %v", err) + } else if ret, err = C.nfq_unbind_pf(q.h, AF_INET6); err != nil || ret < 0 { + return fmt.Errorf("Error unbinding existing q handler from AF_INET6 protocol family: %v", err) + } else if ret, err := C.nfq_bind_pf(q.h, AF_INET); err != nil || ret < 0 { + return fmt.Errorf("Error binding to AF_INET protocol family: %v", err) + } else if ret, err := C.nfq_bind_pf(q.h, AF_INET6); err != nil || ret < 0 { + return fmt.Errorf("Error binding to AF_INET6 protocol family: %v", err) + } else if q.qh, err = C.CreateQueue(q.h, C.u_int16_t(queueID), C.u_int32_t(q.idx)); err != nil || q.qh == nil { + q.destroy() + return fmt.Errorf("Error binding to queue: %v", err) + } + + queueIndexLock.Lock() + queueIndex[q.idx] = &q.packets + queueIndexLock.Unlock() + + return nil +} + +func (q *Queue) setup() (err error) { + var ret C.int + + queueSize := C.u_int32_t(NF_DEFAULT_QUEUE_SIZE) + bufferSize := C.uint(NF_DEFAULT_PACKET_SIZE) + totSize := C.uint(NF_DEFAULT_QUEUE_SIZE * NF_DEFAULT_PACKET_SIZE) + + if ret, err = C.nfq_set_queue_maxlen(q.qh, queueSize); err != nil || ret < 0 { + q.destroy() + return fmt.Errorf("Unable to set max packets in queue: %v", err) + } else if C.nfq_set_mode(q.qh, C.u_int8_t(2), bufferSize) < 0 { + q.destroy() + return fmt.Errorf("Unable to set packets copy mode: %v", err) + } else if q.fd, err = C.nfq_fd(q.h); err != nil { + q.destroy() + return fmt.Errorf("Unable to get queue file-descriptor. %v", err) + } else if C.nfnl_rcvbufsiz(C.nfq_nfnlh(q.h), totSize) < 0 { + q.destroy() + return fmt.Errorf("Unable to increase netfilter buffer space size") + } + + return nil +} + +func (q *Queue) run(exitCh chan<- bool) { + if errno := C.Run(q.h, q.fd); errno != 0 { + fmt.Fprintf(os.Stderr, "Terminating, unable to receive packet due to errno=%d", errno) + } + exitChan <- true +} + +// Close ensures that nfqueue resources are freed and closed. +// C.stop_reading_packets() stops the reading packets loop, which causes +// go-subroutine run() to exit. +// After exit, listening queue is destroyed and closed. +// If for some reason any of the steps stucks while closing it, we'll exit by timeout. +func (q *Queue) Close() { + close(q.packets) + C.stop_reading_packets() + q.destroy() + queueIndexLock.Lock() + delete(queueIndex, q.idx) + queueIndexLock.Unlock() +} + +func (q *Queue) destroy() { + // we'll try to exit cleanly, but sometimes nfqueue gets stuck + time.AfterFunc(5*time.Second, func() { + log.Warning("queue stuck, closing by timeout") + if q != nil { + C.close(q.fd) + q.closeNfq() + } + os.Exit(0) + }) + C.nfq_unbind_pf(q.h, AF_INET) + C.nfq_unbind_pf(q.h, AF_INET6) + if q.qh != nil { + if ret := C.nfq_destroy_queue(q.qh); ret != 0 { + log.Warning("Queue.destroy(), nfq_destroy_queue() not closed: %d", ret) + } + } + + q.closeNfq() +} + +func (q *Queue) closeNfq() { + if q.h != nil { + if ret := C.nfq_close(q.h); ret != 0 { + log.Warning("Queue.destroy(), nfq_close() not closed: %d", ret) + } + } +} + +// Packets return the list of enqueued packets. +func (q *Queue) Packets() <-chan Packet { + return q.packets +} + +// FYI: the export keyword is mandatory to specify that go_callback is defined elsewhere + +//export go_callback +func go_callback(queueID C.int, data *C.uchar, length C.int, mark C.uint, idx uint32, vc *VerdictContainerC, uid uint32) { + (*vc).verdict = C.uint(NF_ACCEPT) + (*vc).data = nil + (*vc).mark_set = 0 + (*vc).length = 0 + + queueIndexLock.RLock() + queueChannel, found := queueIndex[idx] + queueIndexLock.RUnlock() + if !found { + fmt.Fprintf(os.Stderr, "Unexpected queue idx %d\n", idx) + return + } + + xdata := C.GoBytes(unsafe.Pointer(data), length) + + p := Packet{ + verdictChannel: make(chan VerdictContainer), + Mark: uint32(mark), + UID: uid, + NetworkProtocol: xdata[0] >> 4, // first 4 bits is the version + } + + var packet gopacket.Packet + if p.IsIPv4() { + packet = gopacket.NewPacket(xdata, layers.LayerTypeIPv4, gopacketDecodeOptions) + } else { + packet = gopacket.NewPacket(xdata, layers.LayerTypeIPv6, gopacketDecodeOptions) + } + + p.Packet = packet + + select { + case *queueChannel <- p: + select { + case v := <-p.verdictChannel: + if v.Packet == nil { + (*vc).verdict = C.uint(v.Verdict) + } else { + (*vc).verdict = C.uint(v.Verdict) + (*vc).data = (*C.uchar)(unsafe.Pointer(&v.Packet[0])) + (*vc).length = C.uint(len(v.Packet)) + } + + if v.Mark != 0 { + (*vc).mark_set = C.uint(1) + (*vc).mark = C.uint(v.Mark) + } + } + + case <-time.After(1 * time.Millisecond): + fmt.Fprintf(os.Stderr, "Timed out while sending packet to queue channel %d\n", idx) + } +} diff --git a/daemon/netfilter/queue.h b/daemon/netfilter/queue.h new file mode 100644 index 0000000..64c3ea7 --- /dev/null +++ b/daemon/netfilter/queue.h @@ -0,0 +1,113 @@ +#ifndef _NETFILTER_QUEUE_H +#define _NETFILTER_QUEUE_H + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <errno.h> +#include <math.h> +#include <unistd.h> +#include <dlfcn.h> +#include <netinet/in.h> +#include <linux/types.h> +#include <linux/socket.h> +#include <linux/netfilter.h> +#include <libnetfilter_queue/libnetfilter_queue.h> + +typedef struct { + uint verdict; + uint mark; + uint mark_set; + uint length; + unsigned char *data; +} verdictContainer; + +static void *get_uid = NULL; + +extern void go_callback(int id, unsigned char* data, int len, uint mark, u_int32_t idx, verdictContainer *vc, uint32_t uid); + +static uint8_t stop = 0; + +static inline void configure_uid_if_available(struct nfq_q_handle *qh){ + void *hndl = dlopen("libnetfilter_queue.so.1", RTLD_LAZY); + if (!hndl) { + hndl = dlopen("libnetfilter_queue.so", RTLD_LAZY); + if (!hndl){ + printf("WARNING: libnetfilter_queue not available\n"); + return; + } + } + if ((get_uid = dlsym(hndl, "nfq_get_uid")) == NULL){ + printf("WARNING: nfq_get_uid not available\n"); + return; + } + printf("OK: libnetfiler_queue supports nfq_get_uid\n"); +#ifdef NFQA_CFG_F_UID_GID + if (qh != NULL && nfq_set_queue_flags(qh, NFQA_CFG_F_UID_GID, NFQA_CFG_F_UID_GID)){ + printf("WARNING: UID not available on this kernel/libnetfilter_queue\n"); + } +#endif +} + +static int nf_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *arg){ + if (stop) { + return -1; + } + + uint32_t id = -1, idx = 0, mark = 0; + struct nfqnl_msg_packet_hdr *ph = NULL; + unsigned char *buffer = NULL; + int size = 0; + verdictContainer vc = {0}; + uint32_t uid = 0xffffffff; + + mark = nfq_get_nfmark(nfa); + ph = nfq_get_msg_packet_hdr(nfa); + id = ntohl(ph->packet_id); + size = nfq_get_payload(nfa, &buffer); + idx = (uint32_t)((uintptr_t)arg); + +#ifdef NFQA_CFG_F_UID_GID + if (get_uid) + nfq_get_uid(nfa, &uid); +#endif + + go_callback(id, buffer, size, mark, idx, &vc, uid); + + if( vc.mark_set == 1 ) { + return nfq_set_verdict2(qh, id, vc.verdict, vc.mark, vc.length, vc.data); + } + return nfq_set_verdict2(qh, id, vc.verdict, vc.mark, vc.length, vc.data); +} + +static inline struct nfq_q_handle* CreateQueue(struct nfq_handle *h, u_int16_t queue, u_int32_t idx) { + struct nfq_q_handle* qh = nfq_create_queue(h, queue, &nf_callback, (void*)((uintptr_t)idx)); + if (qh == NULL){ + printf("ERROR: nfq_create_queue() queue not created\n"); + } else { + configure_uid_if_available(qh); + } + return qh; +} + +static inline void stop_reading_packets() { + stop = 1; +} + +static inline int Run(struct nfq_handle *h, int fd) { + char buf[4096] __attribute__ ((aligned)); + int rcvd, opt = 1; + + setsockopt(fd, SOL_NETLINK, NETLINK_NO_ENOBUFS, &opt, sizeof(int)); + + while ((rcvd = recv(fd, buf, sizeof(buf), 0)) >= 0) { + if (stop == 1) { + return errno; + } + nfq_handle_packet(h, buf, rcvd); + } + + return errno; +} + +#endif diff --git a/daemon/netlink/socket.go b/daemon/netlink/socket.go new file mode 100644 index 0000000..d9fd001 --- /dev/null +++ b/daemon/netlink/socket.go @@ -0,0 +1,153 @@ +package netlink + +import ( + "fmt" + "net" + "strconv" + "syscall" + + "github.com/evilsocket/opensnitch/daemon/log" +) + +// GetSocketInfo asks the kernel via netlink for a given connection. +// If the connection is found, we return the uid and the possible +// associated inodes. +// If the outgoing connection is not found but there're entries with the source +// port and same protocol, add all the inodes to the list. +// +// Some examples: +// outgoing connection as seen by netfilter || connection details dumped from kernel +// +// 47344:192.168.1.106 -> 151.101.65.140:443 || in kernel: 47344:192.168.1.106 -> 151.101.65.140:443 +// 8612:192.168.1.5 -> 192.168.1.255:8612 || in kernel: 8612:192.168.1.105 -> 0.0.0.0:0 +// 123:192.168.1.5 -> 217.144.138.234:123 || in kernel: 123:0.0.0.0 -> 0.0.0.0:0 +// 45015:127.0.0.1 -> 239.255.255.250:1900 || in kernel: 45015:127.0.0.1 -> 0.0.0.0:0 +// 50416:fe80::9fc2:ddcf:df22:aa50 -> fe80::1:53 || in kernel: 50416:254.128.0.0 -> 254.128.0.0:53 +// 51413:192.168.1.106 -> 103.224.182.250:1337 || in kernel: 51413:0.0.0.0 -> 0.0.0.0:0 +func GetSocketInfo(proto string, srcIP net.IP, srcPort uint, dstIP net.IP, dstPort uint) (uid int, inodes []int) { + uid = -1 + family := uint8(syscall.AF_INET) + ipproto := uint8(syscall.IPPROTO_TCP) + protoLen := len(proto) + if proto[protoLen-1:protoLen] == "6" { + family = syscall.AF_INET6 + } + + if proto[:3] == "udp" { + ipproto = syscall.IPPROTO_UDP + if protoLen >= 7 && proto[:7] == "udplite" { + ipproto = syscall.IPPROTO_UDPLITE + } + } + if sockList, err := SocketGet(family, ipproto, uint16(srcPort), uint16(dstPort), srcIP, dstIP); err == nil { + for n, sock := range sockList { + if sock.UID != 0xffffffff { + uid = int(sock.UID) + } + log.Debug("[%d/%d] outgoing connection uid: %d, %d:%v -> %v:%d || netlink response: %d:%v -> %v:%d inode: %d - loopback: %v multicast: %v unspecified: %v linklocalunicast: %v ifaceLocalMulticast: %v GlobalUni: %v ", + n, len(sockList), + int(sock.UID), + srcPort, srcIP, dstIP, dstPort, + sock.ID.SourcePort, sock.ID.Source, + sock.ID.Destination, sock.ID.DestinationPort, sock.INode, + sock.ID.Destination.IsLoopback(), + sock.ID.Destination.IsMulticast(), + sock.ID.Destination.IsUnspecified(), + sock.ID.Destination.IsLinkLocalUnicast(), + sock.ID.Destination.IsLinkLocalMulticast(), + sock.ID.Destination.IsGlobalUnicast(), + ) + + if sock.ID.SourcePort == uint16(srcPort) && sock.ID.Source.Equal(srcIP) && + (sock.ID.DestinationPort == uint16(dstPort)) && + ((sock.ID.Destination.IsGlobalUnicast() || sock.ID.Destination.IsLoopback()) && sock.ID.Destination.Equal(dstIP)) { + inodes = append([]int{int(sock.INode)}, inodes...) + continue + } + log.Debug("GetSocketInfo() invalid: %d:%v -> %v:%d", sock.ID.SourcePort, sock.ID.Source, sock.ID.Destination, sock.ID.DestinationPort) + } + + // handle special cases (see function description): ntp queries (123), broadcasts, incomming connections. + if len(inodes) == 0 && len(sockList) > 0 { + for n, sock := range sockList { + if sockList[n].ID.Destination.Equal(net.IPv4zero) || sockList[n].ID.Destination.Equal(net.IPv6zero) { + inodes = append([]int{int(sock.INode)}, inodes...) + log.Debug("netlink socket not found, adding entry: %d:%v -> %v:%d || %d:%v -> %v:%d inode: %d state: %s", + srcPort, srcIP, dstIP, dstPort, + sockList[n].ID.SourcePort, sockList[n].ID.Source, + sockList[n].ID.Destination, sockList[n].ID.DestinationPort, + sockList[n].INode, TCPStatesMap[sock.State]) + } else if sock.ID.SourcePort == uint16(srcPort) && sock.ID.Source.Equal(srcIP) && + (sock.ID.DestinationPort == uint16(dstPort)) { + inodes = append([]int{int(sock.INode)}, inodes...) + continue + } else { + log.Debug("netlink socket not found, EXCLUDING entry: %d:%v -> %v:%d || %d:%v -> %v:%d inode: %d state: %s", + srcPort, srcIP, dstIP, dstPort, + sockList[n].ID.SourcePort, sockList[n].ID.Source, + sockList[n].ID.Destination, sockList[n].ID.DestinationPort, + sockList[n].INode, TCPStatesMap[sock.State]) + } + } + } + } else { + log.Debug("netlink socket error: %v - %d:%v -> %v:%d", err, srcPort, srcIP, dstIP, dstPort) + } + + return uid, inodes +} + +// GetSocketInfoByInode dumps the kernel sockets table and searches the given +// inode on it. +func GetSocketInfoByInode(inodeStr string) (*Socket, error) { + inode, err := strconv.ParseUint(inodeStr, 10, 32) + if err != nil { + return nil, err + } + + type inetStruct struct{ family, proto uint8 } + socketTypes := []inetStruct{ + {syscall.AF_INET, syscall.IPPROTO_TCP}, + {syscall.AF_INET, syscall.IPPROTO_UDP}, + {syscall.AF_INET6, syscall.IPPROTO_TCP}, + {syscall.AF_INET6, syscall.IPPROTO_UDP}, + } + + for _, socket := range socketTypes { + socketList, err := SocketsDump(socket.family, socket.proto) + if err != nil { + return nil, err + } + for idx := range socketList { + if uint32(inode) == socketList[idx].INode { + return socketList[idx], nil + } + } + } + return nil, fmt.Errorf("Inode not found") +} + +// KillSocket kills a socket given the properties of a connection. +func KillSocket(proto string, srcIP net.IP, srcPort uint, dstIP net.IP, dstPort uint) { + family := uint8(syscall.AF_INET) + ipproto := uint8(syscall.IPPROTO_TCP) + protoLen := len(proto) + if proto[protoLen-1:protoLen] == "6" { + family = syscall.AF_INET6 + } + + if proto[:3] == "udp" { + ipproto = syscall.IPPROTO_UDP + if protoLen >= 7 && proto[:7] == "udplite" { + ipproto = syscall.IPPROTO_UDPLITE + } + } + + if sockList, err := SocketGet(family, ipproto, uint16(srcPort), uint16(dstPort), srcIP, dstIP); err == nil { + for _, s := range sockList { + if err := socketKill(family, ipproto, s.ID); err != nil { + log.Debug("Unable to kill socket: %d, %d, %v", srcPort, dstPort, err) + } + } + } +} diff --git a/daemon/netlink/socket_linux.go b/daemon/netlink/socket_linux.go new file mode 100644 index 0000000..944f278 --- /dev/null +++ b/daemon/netlink/socket_linux.go @@ -0,0 +1,264 @@ +package netlink + +import ( + "encoding/binary" + "errors" + "fmt" + "net" + "syscall" + + "github.com/evilsocket/opensnitch/daemon/log" + "github.com/vishvananda/netlink/nl" +) + +// This is a modification of https://github.com/vishvananda/netlink socket_linux.go - Apache2.0 license +// which adds support for query UDP, UDPLITE and IPv6 sockets to SocketGet() + +const ( + SOCK_DESTROY = 21 + sizeofSocketID = 0x30 + sizeofSocketRequest = sizeofSocketID + 0x8 + sizeofSocket = sizeofSocketID + 0x18 +) + +var ( + native = nl.NativeEndian() + networkOrder = binary.BigEndian + TCP_ALL = uint32(0xfff) +) + +// https://elixir.bootlin.com/linux/latest/source/include/net/tcp_states.h +const ( + TCP_INVALID = iota + TCP_ESTABLISHED + TCP_SYN_SENT + TCP_SYN_RECV + TCP_FIN_WAIT1 + TCP_FIN_WAIT2 + TCP_TIME_WAIT + TCP_CLOSE + TCP_CLOSE_WAIT + TCP_LAST_ACK + TCP_LISTEN + TCP_CLOSING + TCP_NEW_SYN_REC + TCP_MAX_STATES +) + +// TCPStatesMap holds the list of TCP states +var TCPStatesMap = map[uint8]string{ + TCP_INVALID: "invalid", + TCP_ESTABLISHED: "established", + TCP_SYN_SENT: "syn_sent", + TCP_SYN_RECV: "syn_recv", + TCP_FIN_WAIT1: "fin_wait1", + TCP_FIN_WAIT2: "fin_wait2", + TCP_TIME_WAIT: "time_wait", + TCP_CLOSE: "close", + TCP_CLOSE_WAIT: "close_wait", + TCP_LAST_ACK: "last_ack", + TCP_LISTEN: "listen", + TCP_CLOSING: "closing", +} + +// SocketID holds the socket information of a request/response to the kernel +type SocketID struct { + SourcePort uint16 + DestinationPort uint16 + Source net.IP + Destination net.IP + Interface uint32 + Cookie [2]uint32 +} + +// Socket represents a netlink socket. +type Socket struct { + Family uint8 + State uint8 + Timer uint8 + Retrans uint8 + ID SocketID + Expires uint32 + RQueue uint32 + WQueue uint32 + UID uint32 + INode uint32 +} + +// SocketRequest holds the request/response of a connection to the kernel +type SocketRequest struct { + Family uint8 + Protocol uint8 + Ext uint8 + pad uint8 + States uint32 + ID SocketID +} + +type writeBuffer struct { + Bytes []byte + pos int +} + +func (b *writeBuffer) Write(c byte) { + b.Bytes[b.pos] = c + b.pos++ +} + +func (b *writeBuffer) Next(n int) []byte { + s := b.Bytes[b.pos : b.pos+n] + b.pos += n + return s +} + +// Serialize convert SocketRequest struct to bytes. +func (r *SocketRequest) Serialize() []byte { + b := writeBuffer{Bytes: make([]byte, sizeofSocketRequest)} + b.Write(r.Family) + b.Write(r.Protocol) + b.Write(r.Ext) + b.Write(r.pad) + native.PutUint32(b.Next(4), r.States) + networkOrder.PutUint16(b.Next(2), r.ID.SourcePort) + networkOrder.PutUint16(b.Next(2), r.ID.DestinationPort) + if r.Family == syscall.AF_INET6 { + copy(b.Next(16), r.ID.Source) + copy(b.Next(16), r.ID.Destination) + } else { + copy(b.Next(4), r.ID.Source.To4()) + b.Next(12) + copy(b.Next(4), r.ID.Destination.To4()) + b.Next(12) + } + native.PutUint32(b.Next(4), r.ID.Interface) + native.PutUint32(b.Next(4), r.ID.Cookie[0]) + native.PutUint32(b.Next(4), r.ID.Cookie[1]) + return b.Bytes +} + +// Len returns the size of a socket request +func (r *SocketRequest) Len() int { return sizeofSocketRequest } + +type readBuffer struct { + Bytes []byte + pos int +} + +func (b *readBuffer) Read() byte { + c := b.Bytes[b.pos] + b.pos++ + return c +} + +func (b *readBuffer) Next(n int) []byte { + s := b.Bytes[b.pos : b.pos+n] + b.pos += n + return s +} + +func (s *Socket) deserialize(b []byte) error { + if len(b) < sizeofSocket { + return fmt.Errorf("socket data short read (%d); want %d", len(b), sizeofSocket) + } + rb := readBuffer{Bytes: b} + s.Family = rb.Read() + s.State = rb.Read() + s.Timer = rb.Read() + s.Retrans = rb.Read() + s.ID.SourcePort = networkOrder.Uint16(rb.Next(2)) + s.ID.DestinationPort = networkOrder.Uint16(rb.Next(2)) + if s.Family == syscall.AF_INET6 { + s.ID.Source = net.IP(rb.Next(16)) + s.ID.Destination = net.IP(rb.Next(16)) + } else { + s.ID.Source = net.IPv4(rb.Read(), rb.Read(), rb.Read(), rb.Read()) + rb.Next(12) + s.ID.Destination = net.IPv4(rb.Read(), rb.Read(), rb.Read(), rb.Read()) + rb.Next(12) + } + s.ID.Interface = native.Uint32(rb.Next(4)) + s.ID.Cookie[0] = native.Uint32(rb.Next(4)) + s.ID.Cookie[1] = native.Uint32(rb.Next(4)) + s.Expires = native.Uint32(rb.Next(4)) + s.RQueue = native.Uint32(rb.Next(4)) + s.WQueue = native.Uint32(rb.Next(4)) + s.UID = native.Uint32(rb.Next(4)) + s.INode = native.Uint32(rb.Next(4)) + return nil +} + +// SocketKill kills a connection +func socketKill(family, proto uint8, sockID SocketID) error { + + sockReq := &SocketRequest{ + Family: family, + Protocol: proto, + ID: sockID, + } + + req := nl.NewNetlinkRequest(SOCK_DESTROY, syscall.NLM_F_REQUEST|syscall.NLM_F_ACK) + req.AddData(sockReq) + _, err := req.Execute(syscall.NETLINK_INET_DIAG, 0) + if err != nil { + return err + } + return nil +} + +// SocketGet returns the list of active connections in the kernel +// filtered by several fields. Currently it returns connections +// filtered by source port and protocol. +func SocketGet(family uint8, proto uint8, srcPort, dstPort uint16, local, remote net.IP) ([]*Socket, error) { + _Id := SocketID{ + SourcePort: srcPort, + Cookie: [2]uint32{nl.TCPDIAG_NOCOOKIE, nl.TCPDIAG_NOCOOKIE}, + } + + sockReq := &SocketRequest{ + Family: family, + Protocol: proto, + States: TCP_ALL, + ID: _Id, + } + + return netlinkRequest(sockReq, family, proto, srcPort, dstPort, local, remote) +} + +// SocketsDump returns the list of all connections from the kernel +func SocketsDump(family uint8, proto uint8) ([]*Socket, error) { + sockReq := &SocketRequest{ + Family: family, + Protocol: proto, + States: TCP_ALL, + } + return netlinkRequest(sockReq, 0, 0, 0, 0, nil, nil) +} + +func netlinkRequest(sockReq *SocketRequest, family uint8, proto uint8, srcPort, dstPort uint16, local, remote net.IP) ([]*Socket, error) { + req := nl.NewNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, syscall.NLM_F_DUMP) + req.AddData(sockReq) + msgs, err := req.Execute(syscall.NETLINK_INET_DIAG, 0) + if err != nil { + return nil, err + } + if len(msgs) == 0 { + return nil, errors.New("Warning, no message nor error from netlink, or no connections found") + } + var sock []*Socket + for n, m := range msgs { + s := &Socket{} + if err = s.deserialize(m); err != nil { + log.Error("[%d] netlink socket error: %s, %d:%v -> %v:%d - %d:%v -> %v:%d", + n, TCPStatesMap[s.State], + srcPort, local, remote, dstPort, + s.ID.SourcePort, s.ID.Source, s.ID.Destination, s.ID.DestinationPort) + continue + } + if s.INode == 0 { + continue + } + + sock = append([]*Socket{s}, sock...) + } + return sock, err +} diff --git a/daemon/netlink/socket_test.go b/daemon/netlink/socket_test.go new file mode 100644 index 0000000..b37719b --- /dev/null +++ b/daemon/netlink/socket_test.go @@ -0,0 +1,116 @@ +package netlink + +import ( + "fmt" + "net" + "os" + "strconv" + "strings" + "testing" +) + +type Connection struct { + SrcIP net.IP + DstIP net.IP + Protocol string + SrcPort uint + DstPort uint + OutConn net.Conn + Listener net.Listener +} + +func EstablishConnection(proto, dst string) (net.Conn, error) { + c, err := net.Dial(proto, dst) + if err != nil { + fmt.Println(err) + return nil, err + } + return c, nil +} + +func ListenOnPort(proto, port string) (net.Listener, error) { + // TODO: UDP -> ListenUDP() or ListenPacket() + l, err := net.Listen(proto, port) + if err != nil { + fmt.Println(err) + return nil, err + } + return l, nil +} + +func setupConnection(proto string, connChan chan *Connection) { + listnr, _ := ListenOnPort(proto, "127.0.0.1:55555") + conn, err := EstablishConnection(proto, "127.0.0.1:55555") + if err != nil { + connChan <- nil + return + } + laddr := strings.Split(conn.LocalAddr().String(), ":") + daddr := strings.Split(conn.RemoteAddr().String(), ":") + sport, _ := strconv.Atoi(laddr[1]) + dport, _ := strconv.Atoi(daddr[1]) + + lconn := &Connection{ + SrcPort: uint(sport), + DstPort: uint(dport), + SrcIP: net.ParseIP(laddr[0]), + DstIP: net.ParseIP(daddr[0]), + Protocol: "tcp", + Listener: listnr, + OutConn: conn, + } + connChan <- lconn +} + +// TestNetlinkQueries tests queries to the kernel to get the inode of a connection. +// When using ProcFS as monitor method, we need that value to get the PID of an application. +// We also need it if for any reason auditd or ebpf doesn't return the PID of the application. +// TODO: test all the cases described in the GetSocketInfo() description. +func TestNetlinkTCPQueries(t *testing.T) { + // netlink tests disabled by default, they cause random failures on restricted + // environments. + if os.Getenv("NETLINK_TESTS") == "" { + t.Skip("Skipping netlink tests. Use NETLINK_TESTS=1 to launch these tests.") + } + + connChan := make(chan *Connection) + go setupConnection("tcp", connChan) + conn := <-connChan + if conn == nil { + t.Error("TestParseTCPConnection, conn nil") + } + + var inodes []int + uid := -1 + t.Run("Test GetSocketInfo", func(t *testing.T) { + uid, inodes = GetSocketInfo("tcp", conn.SrcIP, conn.SrcPort, conn.DstIP, conn.DstPort) + + if len(inodes) == 0 { + t.Error("inodes empty") + } + if uid != os.Getuid() { + t.Error("GetSocketInfo UID error:", uid, os.Getuid()) + } + }) + + t.Run("Test GetSocketInfoByInode", func(t *testing.T) { + socket, err := GetSocketInfoByInode(fmt.Sprint(inodes[0])) + if err != nil { + t.Error("GetSocketInfoByInode error:", err) + } + if socket == nil { + t.Error("GetSocketInfoByInode inode not found") + } + if socket.ID.SourcePort != uint16(conn.SrcPort) { + t.Error("GetSocketInfoByInode dstPort error:", socket) + } + if socket.ID.DestinationPort != uint16(conn.DstPort) { + t.Error("GetSocketInfoByInode dstPort error:", socket) + } + if socket.UID != uint32(os.Getuid()) { + t.Error("GetSocketInfoByInode UID error:", socket, os.Getuid()) + } + }) + + conn.Listener.Close() +} diff --git a/daemon/netstat/entry.go b/daemon/netstat/entry.go new file mode 100644 index 0000000..6214a00 --- /dev/null +++ b/daemon/netstat/entry.go @@ -0,0 +1,32 @@ +package netstat + +import ( + "net" +) + +// Entry holds the information of a /proc/net/* entry. +// For example, /proc/net/tcp: +// sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode +// 0: 0100007F:13AD 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 18083222 +type Entry struct { + Proto string + SrcIP net.IP + SrcPort uint + DstIP net.IP + DstPort uint + UserId int + INode int +} + +// NewEntry creates a new entry with values from /proc/net/ +func NewEntry(proto string, srcIP net.IP, srcPort uint, dstIP net.IP, dstPort uint, userId int, iNode int) Entry { + return Entry{ + Proto: proto, + SrcIP: srcIP, + SrcPort: srcPort, + DstIP: dstIP, + DstPort: dstPort, + UserId: userId, + INode: iNode, + } +} diff --git a/daemon/netstat/find.go b/daemon/netstat/find.go new file mode 100644 index 0000000..8560c65 --- /dev/null +++ b/daemon/netstat/find.go @@ -0,0 +1,51 @@ +package netstat + +import ( + "net" + "strings" + + "github.com/evilsocket/opensnitch/daemon/core" + "github.com/evilsocket/opensnitch/daemon/log" +) + +// FindEntry looks for the connection in the list of known connections in ProcFS. +func FindEntry(proto string, srcIP net.IP, srcPort uint, dstIP net.IP, dstPort uint) *Entry { + if entry := findEntryForProtocol(proto, srcIP, srcPort, dstIP, dstPort); entry != nil { + return entry + } + + ipv6Suffix := "6" + if core.IPv6Enabled && strings.HasSuffix(proto, ipv6Suffix) == false { + otherProto := proto + ipv6Suffix + log.Debug("Searching for %s netstat entry instead of %s", otherProto, proto) + if entry := findEntryForProtocol(otherProto, srcIP, srcPort, dstIP, dstPort); entry != nil { + return entry + } + } + + return &Entry{ + Proto: proto, + SrcIP: srcIP, + SrcPort: srcPort, + DstIP: dstIP, + DstPort: dstPort, + UserId: -1, + INode: -1, + } +} + +func findEntryForProtocol(proto string, srcIP net.IP, srcPort uint, dstIP net.IP, dstPort uint) *Entry { + entries, err := Parse(proto) + if err != nil { + log.Warning("Error while searching for %s netstat entry: %s", proto, err) + return nil + } + + for _, entry := range entries { + if srcIP.Equal(entry.SrcIP) && srcPort == entry.SrcPort && dstIP.Equal(entry.DstIP) && dstPort == entry.DstPort { + return &entry + } + } + + return nil +} diff --git a/daemon/netstat/parse.go b/daemon/netstat/parse.go new file mode 100644 index 0000000..7d82279 --- /dev/null +++ b/daemon/netstat/parse.go @@ -0,0 +1,120 @@ +package netstat + +import ( + "bufio" + "encoding/binary" + "fmt" + "net" + "os" + "regexp" + "strconv" + + "github.com/evilsocket/opensnitch/daemon/core" + "github.com/evilsocket/opensnitch/daemon/log" +) + +var ( + parser = regexp.MustCompile(`(?i)` + + `\d+:\s+` + // sl + `([a-f0-9]{8,32}):([a-f0-9]{4})\s+` + // local_address + `([a-f0-9]{8,32}):([a-f0-9]{4})\s+` + // rem_address + `[a-f0-9]{2}\s+` + // st + `[a-f0-9]{8}:[a-f0-9]{8}\s+` + // tx_queue rx_queue + `[a-f0-9]{2}:[a-f0-9]{8}\s+` + // tr tm->when + `[a-f0-9]{8}\s+` + // retrnsmt + `(\d+)\s+` + // uid + `\d+\s+` + // timeout + `(\d+)\s+` + // inode + `.+`) // stuff we don't care about +) + +func decToInt(n string) int { + d, err := strconv.ParseInt(n, 10, 64) + if err != nil { + log.Fatal("Error while parsing %s to int: %s", n, err) + } + return int(d) +} + +func hexToInt(h string) uint { + d, err := strconv.ParseUint(h, 16, 64) + if err != nil { + log.Fatal("Error while parsing %s to int: %s", h, err) + } + return uint(d) +} + +func hexToInt2(h string) (uint, uint) { + if len(h) > 16 { + d, err := strconv.ParseUint(h[:16], 16, 64) + if err != nil { + log.Fatal("Error while parsing %s to int: %s", h[16:], err) + } + d2, err := strconv.ParseUint(h[16:], 16, 64) + if err != nil { + log.Fatal("Error while parsing %s to int: %s", h[16:], err) + } + return uint(d), uint(d2) + } + + d, err := strconv.ParseUint(h, 16, 64) + if err != nil { + log.Fatal("Error while parsing %s to int: %s", h[16:], err) + } + return uint(d), 0 +} + +func hexToIP(h string) net.IP { + n, m := hexToInt2(h) + var ip net.IP + if m != 0 { + ip = make(net.IP, 16) + // TODO: Check if this depends on machine endianness? + binary.LittleEndian.PutUint32(ip, uint32(n>>32)) + binary.LittleEndian.PutUint32(ip[4:], uint32(n)) + binary.LittleEndian.PutUint32(ip[8:], uint32(m>>32)) + binary.LittleEndian.PutUint32(ip[12:], uint32(m)) + } else { + ip = make(net.IP, 4) + binary.LittleEndian.PutUint32(ip, uint32(n)) + } + return ip +} + +// Parse scans and retrieves the opened connections, from /proc/net/ files +func Parse(proto string) ([]Entry, error) { + filename := fmt.Sprintf("/proc/net/%s", proto) + fd, err := os.Open(filename) + if err != nil { + return nil, err + } + defer fd.Close() + + entries := make([]Entry, 0) + scanner := bufio.NewScanner(fd) + for lineno := 0; scanner.Scan(); lineno++ { + // skip column names + if lineno == 0 { + continue + } + + line := core.Trim(scanner.Text()) + m := parser.FindStringSubmatch(line) + if m == nil { + log.Warning("Could not parse netstat line from %s: %s", filename, line) + continue + } + + entries = append(entries, NewEntry( + proto, + hexToIP(m[1]), + hexToInt(m[2]), + hexToIP(m[3]), + hexToInt(m[4]), + decToInt(m[5]), + decToInt(m[6]), + )) + } + + return entries, nil +} diff --git a/daemon/opensnitch.spec b/daemon/opensnitch.spec new file mode 100644 index 0000000..00d9062 --- /dev/null +++ b/daemon/opensnitch.spec @@ -0,0 +1,97 @@ +Name: opensnitch +Version: 1.5.8 +Release: 1%{?dist} +Summary: OpenSnitch is a GNU/Linux application firewall + +License: GPLv3+ +URL: https://github.com/evilsocket/%{name} +Source0: https://github.com/evilsocket/%{name}/releases/download/v%{version}/%{name}_%{version}.orig.tar.gz +#BuildArch: x86_64 + +#BuildRequires: godep +Requires(post): info +Requires(preun): info + +%description +Whenever a program makes a connection, it'll prompt the user to allow or deny +it. + +The user can decide if block the outgoing connection based on properties of +the connection: by port, by uid, by dst ip, by program or a combination +of them. + +These rules can last forever, until the app restart or just one time. + +The GUI allows the user to view live outgoing connections, as well as search +by process, user, host or port. + +%prep +rm -rf %{buildroot} + +%setup + +%build +mkdir -p go/src/github.com/evilsocket +ln -s $(pwd) go/src/github.com/evilsocket/opensnitch +export GOPATH=$(pwd)/go +cd go/src/github.com/evilsocket/opensnitch/ +make protocol +cd go/src/github.com/evilsocket/opensnitch/daemon/ +go mod vendor +go build -o opensnitchd . + +%install +mkdir -p %{buildroot}/usr/bin/ %{buildroot}/usr/lib/systemd/system/ %{buildroot}/etc/opensnitchd/rules %{buildroot}/etc/logrotate.d +sed -i 's/\/usr\/local/\/usr/' daemon/opensnitchd.service +install -m 755 daemon/opensnitchd %{buildroot}/usr/bin/opensnitchd +install -m 644 daemon/opensnitchd.service %{buildroot}/usr/lib/systemd/system/opensnitch.service +install -m 644 debian/opensnitch.logrotate %{buildroot}/etc/logrotate.d/opensnitch + +B="" +if [ -f /etc/opensnitchd/default-config.json ]; then + B="-b" +fi +install -m 644 -b $B daemon/default-config.json %{buildroot}/etc/opensnitchd/default-config.json + +B="" +if [ -f /etc/opensnitchd/system-fw.json ]; then + B="-b" +fi +install -m 644 -b $B daemon/system-fw.json %{buildroot}/etc/opensnitchd/system-fw.json + +install -m 644 ebpf_prog/opensnitch.o %{buildroot}/etc/opensnitchd/opensnitch.o + +# upgrade, uninstall +%preun +systemctl stop opensnitch.service || true + +%post +if [ $1 -eq 1 ]; then + systemctl enable opensnitch.service +fi +systemctl start opensnitch.service + +# uninstall,upgrade +%postun +if [ $1 -eq 0 ]; then + systemctl disable opensnitch.service +fi +if [ $1 -eq 0 -a -f /etc/logrotate.d/opensnitch ]; then + rm /etc/logrotate.d/opensnitch +fi + +# postun is the last step after reinstalling +if [ $1 -eq 1 ]; then + systemctl start opensnitch.service +fi + +%clean +rm -rf %{buildroot} + +%files +%{_bindir}/opensnitchd +/usr/lib/systemd/system/opensnitch.service +%{_sysconfdir}/opensnitchd/default-config.json +%{_sysconfdir}/opensnitchd/system-fw.json +%{_sysconfdir}/opensnitchd/opensnitch.o +%{_sysconfdir}/logrotate.d/opensnitch diff --git a/daemon/opensnitchd.service b/daemon/opensnitchd.service new file mode 100644 index 0000000..6c3e6df --- /dev/null +++ b/daemon/opensnitchd.service @@ -0,0 +1,16 @@ +[Unit] +Description=OpenSnitch is a GNU/Linux port of the Little Snitch application firewall. +Documentation=https://github.com/gustavo-iniguez-goya/opensnitch/wiki +Wants=network.target +After=network.target + +[Service] +Type=simple +PermissionsStartOnly=true +ExecStartPre=/bin/mkdir -p /etc/opensnitchd/rules +ExecStart=/usr/local/bin/opensnitchd -rules-path /etc/opensnitchd/rules +Restart=always +RestartSec=30 + +[Install] +WantedBy=multi-user.target diff --git a/daemon/procmon/activepids.go b/daemon/procmon/activepids.go new file mode 100644 index 0000000..92e7643 --- /dev/null +++ b/daemon/procmon/activepids.go @@ -0,0 +1,89 @@ +package procmon + +import ( + "fmt" + "io/ioutil" + "strconv" + "strings" + "sync" + "time" + + "github.com/evilsocket/opensnitch/daemon/log" +) + +type value struct { + Process *Process + //Starttime uniquely identifies a process, it is the 22nd value in /proc/<PID>/stat + //if another process starts with the same PID, it's Starttime will be unique + Starttime uint64 +} + +var ( + activePids = make(map[uint64]value) + activePidsLock = sync.RWMutex{} +) + +//MonitorActivePids checks that each process in activePids +//is still running and if not running (or another process with the same pid is running), +//removes the pid from activePids +func MonitorActivePids() { + for { + time.Sleep(time.Second) + activePidsLock.Lock() + for k, v := range activePids { + data, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/stat", k)) + if err != nil { + //file does not exists, pid has quit + delete(activePids, k) + pidsCache.delete(int(k)) + continue + } + startTime, err := strconv.ParseInt(strings.Split(string(data), " ")[21], 10, 64) + if err != nil { + log.Error("Could not find or convert Starttime. This should never happen. Please report this incident to the Opensnitch developers: %v", err) + delete(activePids, k) + pidsCache.delete(int(k)) + continue + } + if uint64(startTime) != v.Starttime { + //extremely unlikely: the original process has quit and another process + //was started with the same PID - all this in less than 1 second + log.Error("Same PID but different Starttime. Please report this incident to the Opensnitch developers.") + delete(activePids, k) + pidsCache.delete(int(k)) + continue + } + } + activePidsLock.Unlock() + } +} + +func findProcessInActivePidsCache(pid uint64) *Process { + activePidsLock.Lock() + defer activePidsLock.Unlock() + if value, ok := activePids[pid]; ok { + return value.Process + } + return nil +} + +func addToActivePidsCache(pid uint64, proc *Process) { + + data, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/stat", pid)) + if err != nil { + //most likely the process has quit by now + return + } + startTime, err2 := strconv.ParseInt(strings.Split(string(data), " ")[21], 10, 64) + if err2 != nil { + log.Error("Could not find or convert Starttime. This should never happen. Please report this incident to the Opensnitch developers: %v", err) + return + } + + activePidsLock.Lock() + activePids[pid] = value{ + Process: proc, + Starttime: uint64(startTime), + } + activePidsLock.Unlock() +} diff --git a/daemon/procmon/activepids_test.go b/daemon/procmon/activepids_test.go new file mode 100644 index 0000000..88ca598 --- /dev/null +++ b/daemon/procmon/activepids_test.go @@ -0,0 +1,104 @@ +package procmon + +import ( + "fmt" + "math/rand" + "os" + "os/exec" + "syscall" + "testing" + "time" +) + +//TestMonitorActivePids starts helper processes, adds them to activePids +//and then kills them and checks if monitorActivePids() removed the killed processes +//from activePids +func TestMonitorActivePids(t *testing.T) { + + if os.Getenv("helperBinaryMode") == "on" { + //we are in the "helper binary" mode, we were started with helperCmd.Start() (see below) + //do nothing, just wait to be killed + time.Sleep(time.Second * 10) + os.Exit(1) //will never get here; but keep it here just in case + } + + //we are in a normal "go test" mode + tmpDir := "/tmp/ostest_" + randString() + os.Mkdir(tmpDir, 0777) + fmt.Println("tmp dir", tmpDir) + defer os.RemoveAll(tmpDir) + + go MonitorActivePids() + + //build a "helper binary" with "go test -c -o /tmp/path" and put it into a tmp dir + helperBinaryPath := tmpDir + "/helper1" + goExecutable, _ := exec.LookPath("go") + cmd := exec.Command(goExecutable, "test", "-c", "-o", helperBinaryPath) + if err := cmd.Run(); err != nil { + t.Error("Error running go test -c", err) + } + + var numberOfHelpers = 5 + var helperProcs []*Process + //start helper binaries + for i := 0; i < numberOfHelpers; i++ { + var helperCmd *exec.Cmd + helperCmd = &exec.Cmd{ + Path: helperBinaryPath, + Args: []string{helperBinaryPath}, + Env: []string{"helperBinaryMode=on"}, + } + if err := helperCmd.Start(); err != nil { + t.Error("Error starting helper binary", err) + } + go func() { + helperCmd.Wait() //must Wait(), otherwise the helper process becomes a zombie when kill()ed + }() + + pid := helperCmd.Process.Pid + proc := NewProcess(pid, helperBinaryPath) + helperProcs = append(helperProcs, proc) + addToActivePidsCache(uint64(pid), proc) + } + //sleep to make sure all processes started before we proceed + time.Sleep(time.Second * 1) + //make sure all PIDS are in the cache + for i := 0; i < numberOfHelpers; i++ { + proc := helperProcs[i] + pid := proc.ID + foundProc := findProcessInActivePidsCache(uint64(pid)) + if foundProc == nil { + t.Error("PID not found among active processes", pid) + } + if proc.Path != foundProc.Path || proc.ID != foundProc.ID { + t.Error("PID or path doesn't match with the found process") + } + } + //kill all helpers except for one + for i := 0; i < numberOfHelpers-1; i++ { + if err := syscall.Kill(helperProcs[i].ID, syscall.SIGTERM); err != nil { + t.Error("error in syscall.Kill", err) + } + } + //give the cache time to remove killed processes + time.Sleep(time.Second * 1) + + //make sure only the alive process is in the cache + foundProc := findProcessInActivePidsCache(uint64(helperProcs[numberOfHelpers-1].ID)) + if foundProc == nil { + t.Error("last alive PID is not found among active processes", foundProc) + } + if len(activePids) != 1 { + t.Error("more than 1 active PIDs left in cache") + } +} + +func randString() string { + rand.Seed(time.Now().UnixNano()) + var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + b := make([]rune, 10) + for i := range b { + b[i] = letterRunes[rand.Intn(len(letterRunes))] + } + return string(b) +} diff --git a/daemon/procmon/audit/client.go b/daemon/procmon/audit/client.go new file mode 100644 index 0000000..396cc06 --- /dev/null +++ b/daemon/procmon/audit/client.go @@ -0,0 +1,355 @@ +// Package audit reads auditd events from the builtin af_unix plugin, and parses +// the messages in order to proactively monitor pids which make connections. +// Once a connection is made and redirected to us via NFQUEUE, we +// lookup the connection inode in /proc, and add the corresponding PID with all +// the information of the process to a list of known PIDs. +// +// TODO: Prompt the user to allow/deny a connection/program as soon as it's +// started. +// +// Requisities: +// - install auditd and audispd-plugins +// - enable af_unix plugin /etc/audisp/plugins.d/af_unix.conf (active = yes) +// - auditctl -a always,exit -F arch=b64 -S socket,connect,execve -k opensnitchd +// - increase /etc/audisp/audispd.conf q_depth if there're dropped events +// - set write_logs to no if you don't need/want audit logs to be stored in the disk. +// +// read messages from the pipe to verify that it's working: +// socat unix-connect:/var/run/audispd_events stdio +// +// Audit event fields: +// https://github.com/linux-audit/audit-documentation/blob/master/specs/fields/field-dictionary.csv +// Record types: +// https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/Security_Guide/sec-Audit_Record_Types.html +// +// Documentation: +// https://github.com/linux-audit/audit-documentation +package audit + +import ( + "bufio" + "fmt" + "io" + "net" + "os" + "runtime" + "sort" + "sync" + "time" + + "github.com/evilsocket/opensnitch/daemon/core" + "github.com/evilsocket/opensnitch/daemon/log" +) + +// Event represents an audit event, which in our case can be an event of type +// socket, execve, socketpair or connect. +type Event struct { + Timestamp string // audit(xxxxxxx:nnnn) + Serial string + ProcName string // comm + ProcPath string // exe + ProcCmdLine string // proctitle + ProcDir string // cwd + ProcMode string // mode + TTY string + Pid int + UID int + Gid int + PPid int + EUid int + EGid int + OUid int + OGid int + UserName string // auid + DstHost net.IP + DstPort int + NetFamily string // inet, inet6, local + Success string + INode int + Dev string + Syscall int + Exit int + EventType string + RawEvent string + LastSeen time.Time +} + +// MaxEventAge is the maximum minutes an audit process can live without network activity. +const ( + MaxEventAge = int(10) +) + +var ( + // Lock holds a mutex + Lock sync.RWMutex + ourPid = os.Getpid() + // cache of events + events []*Event + eventsCleaner *time.Ticker + eventsCleanerChan = (chan bool)(nil) + // TODO: EventChan is an output channel where incoming auditd events will be written. + // If a client opens it. + EventChan = (chan Event)(nil) + eventsExitChan = (chan bool)(nil) + auditConn net.Conn + // TODO: we may need arm arch + rule64 = []string{"exit,always", "-F", "arch=b64", "-F", fmt.Sprint("ppid!=", ourPid), "-F", fmt.Sprint("pid!=", ourPid), "-S", "socket,connect", "-k", "opensnitch"} + rule32 = []string{"exit,always", "-F", "arch=b32", "-F", fmt.Sprint("ppid!=", ourPid), "-F", fmt.Sprint("pid!=", ourPid), "-S", "socketcall", "-F", "a0=1", "-k", "opensnitch"} + audispdPath = "/var/run/audispd_events" +) + +// OPENSNITCH_RULES_KEY is the mark we place on every event we are interested in. +const ( + OpensnitchRulesKey = "key=\"opensnitch\"" +) + +// GetEvents returns the list of processes which have opened a connection. +func GetEvents() []*Event { + return events +} + +// GetEventByPid returns an event given a pid. +func GetEventByPid(pid int) *Event { + Lock.RLock() + defer Lock.RUnlock() + + for _, event := range events { + if pid == event.Pid { + return event + } + } + + return nil +} + +// sortEvents sorts received events by time and elapsed time since latest network activity. +// newest PIDs will be placed on top of the list. +func sortEvents() { + sort.Slice(events, func(i, j int) bool { + now := time.Now() + elapsedTimeT := now.Sub(events[i].LastSeen) + elapsedTimeU := now.Sub(events[j].LastSeen) + t := events[i].LastSeen.UnixNano() + u := events[j].LastSeen.UnixNano() + return t > u && elapsedTimeT < elapsedTimeU + }) +} + +// cleanOldEvents deletes the PIDs which do not exist or that are too old to +// live. +// We start searching from the oldest to the newest. +// If the last network activity of a PID has been greater than MaxEventAge, +// then it'll be deleted. +func cleanOldEvents() { + Lock.Lock() + defer Lock.Unlock() + + for n := len(events) - 1; n >= 0; n-- { + now := time.Now() + elapsedTime := now.Sub(events[n].LastSeen) + if int(elapsedTime.Minutes()) >= MaxEventAge { + events = append(events[:n], events[n+1:]...) + continue + } + if core.Exists(fmt.Sprint("/proc/", events[n].Pid)) == false { + events = append(events[:n], events[n+1:]...) + } + } +} + +func deleteEvent(pid int) { + for n := range events { + if events[n].Pid == pid || events[n].PPid == pid { + deleteEventByIndex(n) + break + } + } +} + +func deleteEventByIndex(index int) { + Lock.Lock() + events = append(events[:index], events[index+1:]...) + Lock.Unlock() +} + +// AddEvent adds new event to the list of PIDs which have generate network +// activity. +// If the PID is already in the list, the LastSeen field is updated, to keep +// it alive. +func AddEvent(aevent *Event) { + if aevent == nil { + return + } + Lock.Lock() + defer Lock.Unlock() + + for n := 0; n < len(events); n++ { + if events[n].Pid == aevent.Pid && events[n].Syscall == aevent.Syscall { + if aevent.ProcCmdLine != "" || (aevent.ProcCmdLine == events[n].ProcCmdLine) { + events[n] = aevent + } + events[n].LastSeen = time.Now() + + sortEvents() + return + } + } + aevent.LastSeen = time.Now() + events = append([]*Event{aevent}, events...) +} + +// startEventsCleaner will review if the events in the cache need to be cleaned +// every 5 minutes. +func startEventsCleaner() { + for { + select { + case <-eventsCleanerChan: + goto Exit + case <-eventsCleaner.C: + cleanOldEvents() + } + } +Exit: + log.Debug("audit: cleanerRoutine stopped") +} + +func addRules() bool { + r64 := append([]string{"-A"}, rule64...) + r32 := append([]string{"-A"}, rule32...) + _, err64 := core.Exec("auditctl", r64) + _, err32 := core.Exec("auditctl", r32) + if err64 == nil && err32 == nil { + return true + } + log.Error("Error adding audit rule, err32=%v, err=%v", err32, err64) + return false +} + +func configureSyscalls() { + // XXX: what about a i386 process running on a x86_64 system? + if runtime.GOARCH == "386" { + syscallSOCKET = "1" + syscallCONNECT = "3" + syscallSOCKETPAIR = "8" + } +} + +func deleteRules() bool { + r64 := []string{"-D", "-k", "opensnitch"} + r32 := []string{"-D", "-k", "opensnitch"} + _, err64 := core.Exec("auditctl", r64) + _, err32 := core.Exec("auditctl", r32) + if err64 == nil && err32 == nil { + return true + } + log.Error("Error deleting audit rules, err32=%v, err64=%v", err32, err64) + return false +} + +func checkRules() bool { + // TODO + return true +} + +func checkStatus() bool { + // TODO + return true +} + +// Reader reads events from audisd af_unix pipe plugin. +// If the auditd daemon is stopped or restarted, the reader handle +// is closed, so we need to restablished the connection. +func Reader(r io.Reader, eventChan chan<- Event) { + if r == nil { + log.Error("Error reading auditd events. Is auditd running? is af_unix plugin enabled?") + return + } + reader := bufio.NewReader(r) + go startEventsCleaner() + + for { + select { + case <-eventsExitChan: + goto Exit + default: + buf, _, err := reader.ReadLine() + if err != nil { + if err == io.EOF { + log.Error("AuditReader: auditd stopped, reconnecting in 30s %s", err) + if newReader, err := reconnect(); err == nil { + reader = bufio.NewReader(newReader) + log.Important("Auditd reconnected, continue reading") + } + continue + } + log.Warning("AuditReader: auditd error %s", err) + break + } + + parseEvent(string(buf[0:len(buf)]), eventChan) + } + } +Exit: + log.Debug("audit.Reader() closed") +} + +// StartChannel creates a channel to receive events from Audit. +// Launch audit.Reader() in a goroutine: +// go audit.Reader(c, (chan<- audit.Event)(audit.EventChan)) +func StartChannel() { + EventChan = make(chan Event, 0) +} + +func reconnect() (net.Conn, error) { + deleteRules() + time.Sleep(30 * time.Second) + return connect() +} + +func connect() (net.Conn, error) { + addRules() + // TODO: make the unix socket path configurable + return net.Dial("unix", audispdPath) +} + +// Stop stops listening for events from auditd and delete the auditd rules. +func Stop() { + if auditConn != nil { + if err := auditConn.Close(); err != nil { + log.Warning("audit.Stop() error closing socket: %v", err) + } + } + + if eventsCleaner != nil { + eventsCleaner.Stop() + } + if eventsExitChan != nil { + eventsExitChan <- true + close(eventsExitChan) + } + if eventsCleanerChan != nil { + eventsCleanerChan <- true + close(eventsCleanerChan) + } + + deleteRules() + if EventChan != nil { + close(EventChan) + } +} + +// Start makes a new connection to the audisp af_unix socket. +func Start() (net.Conn, error) { + auditConn, err := connect() + if err != nil { + log.Error("auditd Start() connection error %v", err) + deleteRules() + return nil, err + } + + configureSyscalls() + eventsCleaner = time.NewTicker(time.Minute * 5) + eventsCleanerChan = make(chan bool) + eventsExitChan = make(chan bool) + return auditConn, err +} diff --git a/daemon/procmon/audit/parse.go b/daemon/procmon/audit/parse.go new file mode 100644 index 0000000..a666588 --- /dev/null +++ b/daemon/procmon/audit/parse.go @@ -0,0 +1,298 @@ +package audit + +import ( + "encoding/hex" + "fmt" + "net" + "regexp" + "strconv" + "strings" +) + +var ( + newEvent = false + netEvent = &Event{} + + // RegExp for parse audit messages + // https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/security_guide/sec-understanding_audit_log_files + auditRE, _ = regexp.Compile(`([a-zA-Z0-9\-_]+)=([a-zA-Z0-9:'\-\/\"\.\,_\(\)]+)`) + rawEvent = make(map[string]string) +) + +// amd64 syscalls definition +// if the platform is not amd64, it's redefined on Start() +var ( + syscallSOCKET = "41" + syscallCONNECT = "42" + syscallSOCKETPAIR = "53" + syscallEXECVE = "59" + syscallSOCKETCALL = "102" +) + +// /usr/include/x86_64-linux-gnu/bits/socket_type.h +const ( + sockSTREAM = "1" + sockDGRAM = "2" + sockRAW = "3" + sockSEQPACKET = "5" + sockPACKET = "10" + + // /usr/include/x86_64-linux-gnu/bits/socket.h + pfUNSPEC = "0" + pfLOCAL = "1" // PF_UNIX + pfINET = "2" + pfINET6 = "10" + + // /etc/protocols + protoIP = "0" + protoTCP = "6" + protoUDP = "17" +) + +// https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/Security_Guide/sec-Audit_Record_Types.html +const ( + AuditTypePROCTITLE = "type=PROCTITLE" + AuditTypeCWD = "type=CWD" + AuditTypePATH = "type=PATH" + AuditTypeEXECVE = "type=EXECVE" + AuditTypeSOCKADDR = "type=SOCKADDR" + AuditTypeSOCKETCALL = "type=SOCKETCALL" + AuditTypeEOE = "type=EOE" +) + +var ( + syscallSOCKETstr = fmt.Sprint("syscall=", syscallSOCKET) + syscallCONNECTstr = fmt.Sprint("syscall=", syscallCONNECT) + syscallSOCKETPAIRstr = fmt.Sprint("syscall=", syscallSOCKETPAIR) + syscallEXECVEstr = fmt.Sprint("syscall=", syscallEXECVE) + syscallSOCKETCALLstr = fmt.Sprint("syscall=", syscallSOCKETCALL) +) + +// parseNetLine parses a SOCKADDR message type of the form: +// saddr string: inet6 host:2001:4860:4860::8888 serv:53 +func parseNetLine(line string, decode bool) (family string, dstHost net.IP, dstPort int) { + + // 0:4 - type + // 4:8 - port + // 8:16 - ip + switch family := line[0:4]; family { + // local + // case "0100": + // ipv4 + case "0200": + octet2 := decodeString(line[4:8]) + octet := decodeString(line[8:16]) + host := fmt.Sprint(octet[0], ".", octet[1], ".", octet[2], ".", octet[3]) + fmt.Printf("dest ip: %s -- %s:%s\n", line[4:8], octet2, host) + // ipv6 + //case "0A00": + } + + if decode == true { + line = decodeString(line) + } + pieces := strings.Split(line, " ") + family = pieces[0] + + if family[:4] != "inet" { + return family, dstHost, 0 + } + + if len(pieces) > 1 && pieces[1][:5] == "host:" { + dstHost = net.ParseIP(strings.Split(pieces[1], "host:")[1]) + } + if len(pieces) > 2 && pieces[2][:5] == "serv:" { + _dstPort, err := strconv.Atoi(strings.Split(line, "serv:")[1]) + if err != nil { + dstPort = -1 + } else { + dstPort = _dstPort + } + } + + return family, dstHost, dstPort +} + +// decodeString will try to decode a string encoded in hexadecimal. +// If the string can not be decoded, the original string will be returned. +// In that case, usually it means that it's a non-encoded string. +func decodeString(s string) string { + decoded, err := hex.DecodeString(s) + if err != nil { + return s + } + return fmt.Sprintf("%s", decoded) +} + +// extractFields parsed an audit raw message, and extracts all the fields. +func extractFields(rawMessage string, newEvent *map[string]string) { + Lock.Lock() + defer Lock.Unlock() + + if auditRE == nil { + newEvent = nil + return + } + fieldList := auditRE.FindAllStringSubmatch(rawMessage, -1) + if fieldList == nil { + newEvent = nil + return + } + for _, field := range fieldList { + (*newEvent)[field[1]] = field[2] + } +} + +// populateEvent populates our Event from a raw parsed message. +func populateEvent(aevent *Event, eventFields *map[string]string) *Event { + if aevent == nil { + return nil + } + Lock.Lock() + defer Lock.Unlock() + + for k, v := range *eventFields { + switch k { + //case "a0": + //case "a1": + //case "a2": + case "fam": + if v == "local" { + return nil + } + aevent.NetFamily = v + case "lport": + aevent.DstPort, _ = strconv.Atoi(v) + // TODO + /*case "addr": + fmt.Println("addr: ", v) + case "daddr": + fmt.Println("daddr: ", v) + case "laddr": + aevent.DstHost = net.ParseIP(v) + case "saddr": + parseNetLine(v, true) + fmt.Println("saddr:", v) + */ + case "exe": + aevent.ProcPath = strings.Trim(decodeString(v), "\"") + case "comm": + aevent.ProcName = strings.Trim(decodeString(v), "\"") + // proctitle may be truncated to 128 characters, so don't rely on it, parse /proc/<pid>/instead + //case "proctitle": + // aevent.ProcCmdLine = strings.Trim(decodeString(v), "\"") + case "tty": + aevent.TTY = v + case "pid": + aevent.Pid, _ = strconv.Atoi(v) + case "ppid": + aevent.PPid, _ = strconv.Atoi(v) + case "uid": + aevent.UID, _ = strconv.Atoi(v) + case "gid": + aevent.Gid, _ = strconv.Atoi(v) + case "success": + aevent.Success = v + case "cwd": + aevent.ProcDir = strings.Trim(decodeString(v), "\"") + case "inode": + aevent.INode, _ = strconv.Atoi(v) + case "dev": + aevent.Dev = v + case "mode": + aevent.ProcMode = v + case "ouid": + aevent.OUid, _ = strconv.Atoi(v) + case "ogid": + aevent.OGid, _ = strconv.Atoi(v) + case "syscall": + aevent.Syscall, _ = strconv.Atoi(v) + case "exit": + aevent.Exit, _ = strconv.Atoi(v) + case "type": + aevent.EventType = v + case "msg": + parts := strings.Split(v[6:], ":") + aevent.Timestamp = parts[0] + aevent.Serial = parts[1][:len(parts[1])-1] + } + } + + return aevent +} + +// parseEvent parses an auditd event, discards the unwanted ones, and adds +// the ones we're interested in to an array. +// We're only interested in the socket,socketpair,connect and execve syscalls. +// Events from us are excluded. +// +// When we received an event, we parse and add it to the list as soon as we can. +// If the next messages of the set have additional information, we update the +// event. +func parseEvent(rawMessage string, eventChan chan<- Event) { + if newEvent == false && strings.Index(rawMessage, OpensnitchRulesKey) == -1 { + return + } + + aEvent := make(map[string]string) + if strings.Index(rawMessage, syscallSOCKETstr) != -1 || + strings.Index(rawMessage, syscallCONNECTstr) != -1 || + strings.Index(rawMessage, syscallSOCKETPAIRstr) != -1 || + strings.Index(rawMessage, syscallEXECVEstr) != -1 || + strings.Index(rawMessage, syscallSOCKETCALLstr) != -1 { + + extractFields(rawMessage, &aEvent) + if aEvent == nil { + return + } + newEvent = true + netEvent = &Event{} + netEvent = populateEvent(netEvent, &aEvent) + AddEvent(netEvent) + } else if newEvent == true && strings.Index(rawMessage, AuditTypePROCTITLE) != -1 { + extractFields(rawMessage, &aEvent) + if aEvent == nil { + return + } + netEvent = populateEvent(netEvent, &aEvent) + AddEvent(netEvent) + } else if newEvent == true && strings.Index(rawMessage, AuditTypeCWD) != -1 { + extractFields(rawMessage, &aEvent) + if aEvent == nil { + return + } + netEvent = populateEvent(netEvent, &aEvent) + AddEvent(netEvent) + } else if newEvent == true && strings.Index(rawMessage, AuditTypeEXECVE) != -1 { + extractFields(rawMessage, &aEvent) + if aEvent == nil { + return + } + netEvent = populateEvent(netEvent, &aEvent) + AddEvent(netEvent) + } else if newEvent == true && strings.Index(rawMessage, AuditTypePATH) != -1 { + extractFields(rawMessage, &aEvent) + if aEvent == nil { + return + } + netEvent = populateEvent(netEvent, &aEvent) + AddEvent(netEvent) + } else if newEvent == true && strings.Index(rawMessage, AuditTypeSOCKADDR) != -1 { + extractFields(rawMessage, &aEvent) + if aEvent == nil { + return + } + + netEvent = populateEvent(netEvent, &aEvent) + AddEvent(netEvent) + if EventChan != nil { + eventChan <- *netEvent + } + } else if newEvent == true && strings.Index(rawMessage, AuditTypeEOE) != -1 { + newEvent = false + AddEvent(netEvent) + if EventChan != nil { + eventChan <- *netEvent + } + } +} diff --git a/daemon/procmon/cache.go b/daemon/procmon/cache.go new file mode 100644 index 0000000..395fc42 --- /dev/null +++ b/daemon/procmon/cache.go @@ -0,0 +1,339 @@ +package procmon + +import ( + "fmt" + "os" + "sort" + "sync" + "time" + + "github.com/evilsocket/opensnitch/daemon/core" +) + +// InodeItem represents an item of the InodesCache. +type InodeItem struct { + sync.RWMutex + + Pid int + FdPath string + LastSeen int64 +} + +// ProcItem represents an item of the pidsCache +type ProcItem struct { + sync.RWMutex + + Pid int + FdPath string + Descriptors []string + LastSeen int64 +} + +// CacheProcs holds the cache of processes that have established connections. +type CacheProcs struct { + sync.RWMutex + items []*ProcItem +} + +// CacheInodes holds the cache of Inodes. +// The key is formed as follow: +// inode+srcip+srcport+dstip+dstport +type CacheInodes struct { + sync.RWMutex + items map[string]*InodeItem +} + +var ( + // cache of inodes, which help to not iterate over all the pidsCache and + // descriptors of /proc/<pid>/fd/ + // 15-50us vs 50-80ms + // we hit this cache when: + // - we've blocked a connection and the process retries it several times until it gives up, + // - or when a process timeouts connecting to an IP/domain and it retries it again, + // - or when a process resolves a domain and then connects to the IP. + inodesCache = NewCacheOfInodes() + maxTTL = 3 // maximum 3 minutes of inactivity in cache. Really rare, usually they lasts less than a minute. + + // 2nd cache of already known running pids, which also saves time by + // iterating only over a few pids' descriptors, (30us-20ms vs. 50-80ms) + // since it's more likely that most of the connections will be made by the + // same (running) processes. + // The cache is ordered by time, placing in the first places those PIDs with + // active connections. + pidsCache CacheProcs + pidsDescriptorsCache = make(map[int][]string) + + cacheTicker = time.NewTicker(2 * time.Minute) +) + +// CacheCleanerTask checks periodically if the inodes in the cache must be removed. +func CacheCleanerTask() { + for { + select { + case <-cacheTicker.C: + inodesCache.cleanup() + } + } +} + +// NewCacheOfInodes returns a new cache for inodes. +func NewCacheOfInodes() *CacheInodes { + return &CacheInodes{ + items: make(map[string]*InodeItem), + } +} + +//****************************************************************************** +// items of the caches. + +func (i *InodeItem) updateTime() { + i.Lock() + i.LastSeen = time.Now().UnixNano() + i.Unlock() +} + +func (i *InodeItem) getTime() int64 { + i.RLock() + defer i.RUnlock() + return i.LastSeen +} + +func (p *ProcItem) updateTime() { + p.Lock() + p.LastSeen = time.Now().UnixNano() + p.Unlock() +} + +func (p *ProcItem) updateDescriptors(descriptors []string) { + p.Lock() + p.Descriptors = descriptors + p.Unlock() +} + +//****************************************************************************** +// cache of processes + +func (c *CacheProcs) add(fdPath string, fdList []string, pid int) { + c.Lock() + defer c.Unlock() + for n := range c.items { + item := c.items[n] + if item == nil { + continue + } + if item.Pid == pid { + item.updateTime() + return + } + } + + procItem := &ProcItem{ + Pid: pid, + FdPath: fdPath, + Descriptors: fdList, + LastSeen: time.Now().UnixNano(), + } + + c.setItems([]*ProcItem{procItem}, c.items) +} + +func (c *CacheProcs) sort(pid int) { + item := c.getItem(0) + if item != nil && item.Pid == pid { + return + } + c.RLock() + defer c.RUnlock() + + sort.Slice(c.items, func(i, j int) bool { + t := c.items[i].LastSeen + u := c.items[j].LastSeen + return t > u || t == u + }) +} + +func (c *CacheProcs) delete(pid int) { + c.Lock() + defer c.Unlock() + + for n, procItem := range c.items { + if procItem.Pid == pid { + c.deleteItem(n) + inodesCache.delete(pid) + break + } + } +} + +func (c *CacheProcs) deleteItem(pos int) { + nItems := len(c.items) + if pos < nItems { + c.setItems(c.items[:pos], c.items[pos+1:]) + } +} + +func (c *CacheProcs) setItems(newItems []*ProcItem, oldItems []*ProcItem) { + c.items = append(newItems, oldItems...) +} + +func (c *CacheProcs) getItem(index int) *ProcItem { + c.RLock() + defer c.RUnlock() + + if index >= len(c.items) { + return nil + } + + return c.items[index] +} + +func (c *CacheProcs) getItems() []*ProcItem { + return c.items +} + +func (c *CacheProcs) countItems() int { + c.RLock() + defer c.RUnlock() + + return len(c.items) +} + +// loop over the processes that have generated connections +func (c *CacheProcs) getPid(inode int, inodeKey string, expect string) (int, int) { + c.Lock() + defer c.Unlock() + + for n, procItem := range c.items { + if procItem == nil { + continue + } + + if idxDesc, _ := getPidDescriptorsFromCache(procItem.FdPath, inodeKey, expect, &procItem.Descriptors, procItem.Pid); idxDesc != -1 { + procItem.updateTime() + return procItem.Pid, n + } + + descriptors := lookupPidDescriptors(procItem.FdPath, procItem.Pid) + if descriptors == nil { + c.deleteItem(n) + continue + } + + procItem.updateDescriptors(descriptors) + if idxDesc, _ := getPidDescriptorsFromCache(procItem.FdPath, inodeKey, expect, &descriptors, procItem.Pid); idxDesc != -1 { + procItem.updateTime() + return procItem.Pid, n + } + } + + return -1, -1 +} + +//****************************************************************************** +// cache of inodes + +func (i *CacheInodes) add(key, descLink string, pid int) { + i.Lock() + defer i.Unlock() + + if descLink == "" { + descLink = fmt.Sprint("/proc/", pid, "/exe") + } + i.items[key] = &InodeItem{ + FdPath: descLink, + Pid: pid, + LastSeen: time.Now().UnixNano(), + } +} + +func (i *CacheInodes) delete(pid int) { + i.Lock() + defer i.Unlock() + + for k, inodeItem := range i.items { + if inodeItem.Pid == pid { + delete(i.items, k) + } + } +} + +func (i *CacheInodes) getPid(inodeKey string) int { + if item, ok := i.isInCache(inodeKey); ok { + // sometimes the process may have disappeared at this point + if _, err := os.Lstat(item.FdPath); err == nil { + item.updateTime() + return item.Pid + } + pidsCache.delete(item.Pid) + i.delItem(inodeKey) + } + + return -1 +} + +func (i *CacheInodes) delItem(inodeKey string) { + i.Lock() + defer i.Unlock() + delete(i.items, inodeKey) +} + +func (i *CacheInodes) getItem(inodeKey string) *InodeItem { + i.RLock() + defer i.RUnlock() + + return i.items[inodeKey] +} + +func (i *CacheInodes) getItems() map[string]*InodeItem { + i.RLock() + defer i.RUnlock() + + return i.items +} + +func (i *CacheInodes) isInCache(inodeKey string) (*InodeItem, bool) { + i.RLock() + defer i.RUnlock() + + if item, found := i.items[inodeKey]; found { + return item, true + } + return nil, false +} + +func (i *CacheInodes) cleanup() { + now := time.Now() + i.Lock() + defer i.Unlock() + for k := range i.items { + if i.items[k] == nil { + continue + } + lastSeen := now.Sub( + time.Unix(0, i.items[k].getTime()), + ) + if core.Exists(i.items[k].FdPath) == false || int(lastSeen.Minutes()) > maxTTL { + delete(i.items, k) + } + } +} + +func getPidDescriptorsFromCache(fdPath, inodeKey, expect string, descriptors *[]string, pid int) (int, *[]string) { + for fdIdx := 0; fdIdx < len(*descriptors); fdIdx++ { + descLink := fmt.Sprint(fdPath, (*descriptors)[fdIdx]) + if link, err := os.Readlink(descLink); err == nil && link == expect { + if fdIdx > 0 { + // reordering helps to reduce look up times by a factor of 10. + fd := (*descriptors)[fdIdx] + *descriptors = append((*descriptors)[:fdIdx], (*descriptors)[fdIdx+1:]...) + *descriptors = append([]string{fd}, *descriptors...) + } + if _, ok := inodesCache.isInCache(inodeKey); ok { + inodesCache.add(inodeKey, descLink, pid) + } + return fdIdx, descriptors + } + } + + return -1, descriptors +} diff --git a/daemon/procmon/cache_test.go b/daemon/procmon/cache_test.go new file mode 100644 index 0000000..5a7cd17 --- /dev/null +++ b/daemon/procmon/cache_test.go @@ -0,0 +1,103 @@ +package procmon + +import ( + "fmt" + "testing" + "time" +) + +func TestCacheProcs(t *testing.T) { + fdList := []string{"0", "1", "2"} + pidsCache.add(fmt.Sprint("/proc/", myPid, "/fd/"), fdList, myPid) + t.Log("Pids in cache: ", pidsCache.countItems()) + + t.Run("Test addProcEntry", func(t *testing.T) { + if pidsCache.countItems() != 1 { + t.Error("pidsCache should be 1") + } + }) + + oldPid := pidsCache.getItem(0) + pidsCache.add(fmt.Sprint("/proc/", myPid, "/fd/"), fdList, myPid) + t.Run("Test addProcEntry update", func(t *testing.T) { + if pidsCache.countItems() != 1 { + t.Error("pidsCache should still be 1!", pidsCache) + } + oldTime := time.Unix(0, oldPid.LastSeen) + newTime := time.Unix(0, pidsCache.getItem(0).LastSeen) + if oldTime.Equal(newTime) == false { + t.Error("pidsCache, time not updated: ", oldTime, newTime) + } + }) + + pidsCache.add("/proc/2/fd", fdList, 2) + pidsCache.delete(2) + t.Run("Test deleteProcEntry", func(t *testing.T) { + if pidsCache.countItems() != 1 { + t.Error("pidsCache should be 1:", pidsCache.countItems()) + } + }) + + pid, _ := pidsCache.getPid(0, "", "/dev/null") + t.Run("Test getPidFromCache", func(t *testing.T) { + if pid != myPid { + t.Error("pid not found in cache", pidsCache.countItems()) + } + }) + + // should not crash, and the number of items should still be 1 + pidsCache.deleteItem(1) + t.Run("Test deleteItem check bounds", func(t *testing.T) { + if pidsCache.countItems() != 1 { + t.Error("deleteItem check bounds error", pidsCache.countItems()) + } + }) + + pidsCache.deleteItem(0) + t.Run("Test deleteItem", func(t *testing.T) { + if pidsCache.countItems() != 0 { + t.Error("deleteItem error", pidsCache.countItems()) + } + }) + t.Log("items in cache:", pidsCache.countItems()) + + // the key of an inodeCache entry is formed as: inodeNumer + srcIP + srcPort + dstIP + dstPort + inodeKey := "000000000127.0.0.144444127.0.0.153" + // add() expects a path to the inode fd (/proc/<pid>/fd/12345), but as getPid() will check the path in order to retrieve the pid, + // we just set it to "" and it'll use /proc/<pid>/exe + inodesCache.add(inodeKey, "", myPid) + t.Run("Test addInodeEntry", func(t *testing.T) { + if _, found := inodesCache.items[inodeKey]; !found { + t.Error("inodesCache, inode not added:", len(inodesCache.items), inodesCache.items) + } + }) + + pid = inodesCache.getPid(inodeKey) + t.Run("Test getPidByInodeFromCache", func(t *testing.T) { + if pid != myPid { + t.Error("inode not found in cache", pid, inodeKey, len(inodesCache.items), inodesCache.items) + } + }) + + // should delete all inodes of a pid + inodesCache.delete(myPid) + t.Run("Test deleteInodeEntry", func(t *testing.T) { + if _, found := inodesCache.items[inodeKey]; found { + t.Error("inodesCache, key found in cache but it should not exist", inodeKey, len(inodesCache.items), inodesCache.items) + } + }) +} + +// Test getPidDescriptorsFromCache descriptors (inodes) reordering. +// When an inode (descriptor) is found, if it's pushed to the top of the list, +// the next time we look for it will cost -10x. +// Without reordering, the inode 0 will always be found on the 10th position, +// taking an average of 100us instead of 30. +// Benchmark results with reordering: ~5600ns/op, without: ~56000ns/op. +func BenchmarkGetPid(b *testing.B) { + fdList := []string{"10", "9", "8", "7", "6", "5", "4", "3", "2", "1", "0"} + pidsCache.add(fmt.Sprint("/proc/", myPid, "/fd/"), fdList, myPid) + for i := 0; i < b.N; i++ { + pidsCache.getPid(0, "", "/dev/null") + } +} diff --git a/daemon/procmon/details.go b/daemon/procmon/details.go new file mode 100644 index 0000000..a69abb3 --- /dev/null +++ b/daemon/procmon/details.go @@ -0,0 +1,197 @@ +package procmon + +import ( + "bufio" + "fmt" + "io/ioutil" + "os" + "regexp" + "strconv" + "strings" + + "github.com/evilsocket/opensnitch/daemon/core" + "github.com/evilsocket/opensnitch/daemon/dns" + "github.com/evilsocket/opensnitch/daemon/netlink" +) + +var socketsRegex, _ = regexp.Compile(`socket:\[([0-9]+)\]`) + +// GetInfo collects information of a process. +func (p *Process) GetInfo() error { + if err := p.readPath(); err != nil { + return err + } + p.readCwd() + p.readCmdline() + p.readEnv() + p.readDescriptors() + p.readIOStats() + p.readStatus() + p.cleanPath() + + return nil +} + +func (p *Process) setCwd(cwd string) { + p.CWD = cwd +} + +func (p *Process) readComm() error { + data, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/comm", p.ID)) + if err != nil { + return err + } + p.Comm = core.Trim(string(data)) + return nil +} + +func (p *Process) readCwd() error { + link, err := os.Readlink(fmt.Sprintf("/proc/%d/cwd", p.ID)) + if err != nil { + return err + } + p.CWD = link + return nil +} + +// read and parse environment variables of a process. +func (p *Process) readEnv() { + if data, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/environ", p.ID)); err == nil { + for _, s := range strings.Split(string(data), "\x00") { + parts := strings.SplitN(core.Trim(s), "=", 2) + if parts != nil && len(parts) == 2 { + key := core.Trim(parts[0]) + val := core.Trim(parts[1]) + p.Env[key] = val + } + } + } +} + +func (p *Process) readPath() error { + linkName := fmt.Sprint("/proc/", p.ID, "/exe") + if _, err := os.Lstat(linkName); err != nil { + return err + } + + if link, err := os.Readlink(linkName); err == nil { + p.Path = link + } + + return nil +} + +func (p *Process) readCmdline() { + if data, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/cmdline", p.ID)); err == nil { + if len(data) == 0 { + return + } + for i, b := range data { + if b == 0x00 { + data[i] = byte(' ') + } + } + + p.Args = make([]string, 0) + + args := strings.Split(string(data), " ") + for _, arg := range args { + arg = core.Trim(arg) + if arg != "" { + p.Args = append(p.Args, arg) + } + } + } +} + +func (p *Process) readDescriptors() { + f, err := os.Open(fmt.Sprint("/proc/", p.ID, "/fd/")) + if err != nil { + return + } + fDesc, err := f.Readdir(-1) + f.Close() + p.Descriptors = nil + + for _, fd := range fDesc { + tempFd := &procDescriptors{ + Name: fd.Name(), + } + if link, err := os.Readlink(fmt.Sprint("/proc/", p.ID, "/fd/", fd.Name())); err == nil { + tempFd.SymLink = link + socket := socketsRegex.FindStringSubmatch(link) + if len(socket) > 0 { + socketInfo, err := netlink.GetSocketInfoByInode(socket[1]) + if err == nil { + tempFd.SymLink = fmt.Sprintf("socket:[%s] - %d:%s -> %s:%d, state: %s", fd.Name(), + socketInfo.ID.SourcePort, + socketInfo.ID.Source.String(), + dns.HostOr(socketInfo.ID.Destination, socketInfo.ID.Destination.String()), + socketInfo.ID.DestinationPort, + netlink.TCPStatesMap[socketInfo.State]) + } + } + + if linkInfo, err := os.Lstat(link); err == nil { + tempFd.Size = linkInfo.Size() + tempFd.ModTime = linkInfo.ModTime() + } + } + p.Descriptors = append(p.Descriptors, tempFd) + } +} + +func (p *Process) readIOStats() { + f, err := os.Open(fmt.Sprint("/proc/", p.ID, "/io")) + if err != nil { + return + } + defer f.Close() + + p.IOStats = &procIOstats{} + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + s := strings.Split(scanner.Text(), " ") + switch s[0] { + case "rchar:": + p.IOStats.RChar, _ = strconv.ParseInt(s[1], 10, 64) + case "wchar:": + p.IOStats.WChar, _ = strconv.ParseInt(s[1], 10, 64) + case "syscr:": + p.IOStats.SyscallRead, _ = strconv.ParseInt(s[1], 10, 64) + case "syscw:": + p.IOStats.SyscallWrite, _ = strconv.ParseInt(s[1], 10, 64) + case "read_bytes:": + p.IOStats.ReadBytes, _ = strconv.ParseInt(s[1], 10, 64) + case "write_bytes:": + p.IOStats.WriteBytes, _ = strconv.ParseInt(s[1], 10, 64) + } + } +} + +func (p *Process) readStatus() { + if data, err := ioutil.ReadFile(fmt.Sprint("/proc/", p.ID, "/status")); err == nil { + p.Status = string(data) + } + if data, err := ioutil.ReadFile(fmt.Sprint("/proc/", p.ID, "/stat")); err == nil { + p.Stat = string(data) + } + if data, err := ioutil.ReadFile(fmt.Sprint("/proc/", p.ID, "/stack")); err == nil { + p.Stack = string(data) + } + if data, err := ioutil.ReadFile(fmt.Sprint("/proc/", p.ID, "/maps")); err == nil { + p.Maps = string(data) + } + if data, err := ioutil.ReadFile(fmt.Sprint("/proc/", p.ID, "/statm")); err == nil { + p.Statm = &procStatm{} + fmt.Sscanf(string(data), "%d %d %d %d %d %d %d", &p.Statm.Size, &p.Statm.Resident, &p.Statm.Shared, &p.Statm.Text, &p.Statm.Lib, &p.Statm.Data, &p.Statm.Dt) + } +} + +func (p *Process) cleanPath() { + pathLen := len(p.Path) + if pathLen >= 10 && p.Path[pathLen-10:] == " (deleted)" { + p.Path = p.Path[:len(p.Path)-10] + } +} diff --git a/daemon/procmon/ebpf/cache.go b/daemon/procmon/ebpf/cache.go new file mode 100644 index 0000000..e408be4 --- /dev/null +++ b/daemon/procmon/ebpf/cache.go @@ -0,0 +1,118 @@ +package ebpf + +import ( + "sync" + "time" +) + +type ebpfCacheItem struct { + Key []byte + LastSeen int64 + UID int + Pid int + Hits uint +} + +type ebpfCacheType struct { + Items map[string]*ebpfCacheItem + sync.RWMutex +} + +var ( + maxTTL = 20 // Seconds + maxCacheItems = 5000 + ebpfCache *ebpfCacheType + ebpfCacheTicker *time.Ticker +) + +// NewEbpfCacheItem creates a new cache item. +func NewEbpfCacheItem(key []byte, pid, uid int) *ebpfCacheItem { + return &ebpfCacheItem{ + Key: key, + Hits: 1, + Pid: pid, + UID: uid, + LastSeen: time.Now().UnixNano(), + } +} + +func (i *ebpfCacheItem) isValid() bool { + lastSeen := time.Now().Sub( + time.Unix(0, i.LastSeen), + ) + return int(lastSeen.Seconds()) < maxTTL +} + +// NewEbpfCache creates a new cache store. +func NewEbpfCache() *ebpfCacheType { + ebpfCacheTicker = time.NewTicker(1 * time.Minute) + return &ebpfCacheType{ + Items: make(map[string]*ebpfCacheItem, 0), + } +} + +func (e *ebpfCacheType) addNewItem(key string, itemKey []byte, pid, uid int) { + e.Lock() + defer e.Unlock() + + e.Items[key] = NewEbpfCacheItem(itemKey, pid, uid) +} + +func (e *ebpfCacheType) isInCache(key string) (item *ebpfCacheItem, found bool) { + leng := e.Len() + + e.Lock() + item, found = e.Items[key] + if found { + if item.isValid() { + e.update(key, item) + } else { + found = false + delete(e.Items, key) + } + } + e.Unlock() + + if leng > maxCacheItems { + e.DeleteOldItems() + } + return +} + +func (e *ebpfCacheType) update(key string, item *ebpfCacheItem) { + item.Hits++ + item.LastSeen = time.Now().UnixNano() + e.Items[key] = item +} + +func (e *ebpfCacheType) Len() int { + e.RLock() + defer e.RUnlock() + return len(e.Items) +} + +func (e *ebpfCacheType) DeleteOldItems() { + length := e.Len() + + e.Lock() + defer e.Unlock() + + for k, item := range e.Items { + if length > maxCacheItems || !item.isValid() { + delete(e.Items, k) + } + } +} + +func (e *ebpfCacheType) clear() { + if e == nil { + return + } + for k := range e.Items { + delete(e.Items, k) + } + + if ebpfCacheTicker != nil { + ebpfCacheTicker.Stop() + } +} diff --git a/daemon/procmon/ebpf/debug.go b/daemon/procmon/ebpf/debug.go new file mode 100644 index 0000000..b423768 --- /dev/null +++ b/daemon/procmon/ebpf/debug.go @@ -0,0 +1,102 @@ +package ebpf + +import ( + "fmt" + "os/exec" + "strconv" + "syscall" + "unsafe" + + "github.com/evilsocket/opensnitch/daemon/log" + daemonNetlink "github.com/evilsocket/opensnitch/daemon/netlink" + elf "github.com/iovisor/gobpf/elf" +) + +// print map contents. used only for debugging +func dumpMap(bpfmap *elf.Map, isIPv6 bool) { + var lookupKey []byte + var nextKey []byte + var value []byte + if !isIPv6 { + lookupKey = make([]byte, 12) + nextKey = make([]byte, 12) + value = make([]byte, 24) + } else { + lookupKey = make([]byte, 36) + nextKey = make([]byte, 36) + value = make([]byte, 24) + } + firstrun := true + i := 0 + for { + i++ + ok, err := m.LookupNextElement(bpfmap, unsafe.Pointer(&lookupKey[0]), + unsafe.Pointer(&nextKey[0]), unsafe.Pointer(&value[0])) + if err != nil { + log.Error("eBPF LookupNextElement error: %v", err) + return + } + if firstrun { + // on first run lookupKey is a dummy, nothing to delete + firstrun = false + copy(lookupKey, nextKey) + continue + } + fmt.Println("key, value", lookupKey, value) + + if !ok { //reached end of map + break + } + copy(lookupKey, nextKey) + } +} + +//PrintEverything prints all the stats. used only for debugging +func PrintEverything() { + bash, _ := exec.LookPath("bash") + //get the number of the first map + out, err := exec.Command(bash, "-c", "bpftool map show | head -n 1 | cut -d ':' -f1").Output() + if err != nil { + fmt.Println("bpftool map dump name tcpMap ", err) + } + i, _ := strconv.Atoi(string(out[:len(out)-1])) + fmt.Println("i is", i) + + //dump all maps for analysis + for j := i; j < i+14; j++ { + _, _ = exec.Command(bash, "-c", "bpftool map dump id "+strconv.Itoa(j)+" > dump"+strconv.Itoa(j)).Output() + } + + alreadyEstablished.RLock() + for sock1, v := range alreadyEstablished.TCP { + fmt.Println(*sock1, v) + } + + fmt.Println("---------------------") + for sock1, v := range alreadyEstablished.TCPv6 { + fmt.Println(*sock1, v) + } + alreadyEstablished.RUnlock() + + fmt.Println("---------------------") + sockets, _ := daemonNetlink.SocketsDump(syscall.AF_INET, syscall.IPPROTO_TCP) + for idx := range sockets { + fmt.Println("socket tcp: ", sockets[idx]) + } + fmt.Println("---------------------") + sockets, _ = daemonNetlink.SocketsDump(syscall.AF_INET6, syscall.IPPROTO_TCP) + for idx := range sockets { + fmt.Println("socket tcp6: ", sockets[idx]) + } + fmt.Println("---------------------") + sockets, _ = daemonNetlink.SocketsDump(syscall.AF_INET, syscall.IPPROTO_UDP) + for idx := range sockets { + fmt.Println("socket udp: ", sockets[idx]) + } + fmt.Println("---------------------") + sockets, _ = daemonNetlink.SocketsDump(syscall.AF_INET6, syscall.IPPROTO_UDP) + for idx := range sockets { + fmt.Println("socket udp6: ", sockets[idx]) + } + +} diff --git a/daemon/procmon/ebpf/ebpf.go b/daemon/procmon/ebpf/ebpf.go new file mode 100644 index 0000000..a04781d --- /dev/null +++ b/daemon/procmon/ebpf/ebpf.go @@ -0,0 +1,188 @@ +package ebpf + +import ( + "encoding/binary" + "fmt" + "net" + "sync" + "syscall" + "unsafe" + + "github.com/evilsocket/opensnitch/daemon/core" + "github.com/evilsocket/opensnitch/daemon/log" + daemonNetlink "github.com/evilsocket/opensnitch/daemon/netlink" + "github.com/evilsocket/opensnitch/daemon/procmon" + elf "github.com/iovisor/gobpf/elf" +) + +//contains pointers to ebpf maps for a given protocol (tcp/udp/v6) +type ebpfMapsForProto struct { + counterMap *elf.Map + bpfmap *elf.Map +} + +//Not in use, ~4usec faster lookup compared to m.LookupElement() + +//mimics union bpf_attr's anonymous struct used by BPF_MAP_*_ELEM commands +//from <linux_headers>/include/uapi/linux/bpf.h +type bpf_lookup_elem_t struct { + map_fd uint64 //even though in bpf.h its type is __u32, we must make it 8 bytes long + //because "key" is of type __aligned_u64, i.e. "key" must be aligned on an 8-byte boundary + key uintptr + value uintptr +} + +type alreadyEstablishedConns struct { + TCP map[*daemonNetlink.Socket]int + TCPv6 map[*daemonNetlink.Socket]int + sync.RWMutex +} + +var ( + m *elf.Module + lock = sync.RWMutex{} + mapSize = uint(12000) + ebpfMaps map[string]*ebpfMapsForProto + //connections which were established at the time when opensnitch started + alreadyEstablished = alreadyEstablishedConns{ + TCP: make(map[*daemonNetlink.Socket]int), + TCPv6: make(map[*daemonNetlink.Socket]int), + } + + //stop == true is a signal for all goroutines to stop + stop = false + + // list of local addresses of this machine + localAddresses []net.IP + + hostByteOrder binary.ByteOrder +) + +//Start installs ebpf kprobes +func Start() error { + + if err := mountDebugFS(); err != nil { + log.Error("ebpf.Start -> mount debugfs error. Report on github please: %s", err) + return err + } + + m = elf.NewModule("/etc/opensnitchd/opensnitch.o") + if err := m.Load(nil); err != nil { + log.Error("eBPF Failed to load /etc/opensnitchd/opensnitch.o: %v", err) + return err + } + + // if previous shutdown was unclean, then we must remove the dangling kprobe + // and install it again (close the module and load it again) + if err := m.EnableKprobes(0); err != nil { + m.Close() + if err := m.Load(nil); err != nil { + log.Error("eBPF failed to load /etc/opensnitchd/opensnitch.o (2): %v", err) + return err + } + if err := m.EnableKprobes(0); err != nil { + log.Error("eBPF error when enabling kprobes: %v", err) + return err + } + } + + // init all connection counters to 0 + zeroKey := make([]byte, 4) + zeroValue := make([]byte, 8) + for _, name := range []string{"tcpcounter", "tcpv6counter", "udpcounter", "udpv6counter"} { + err := m.UpdateElement(m.Map(name), unsafe.Pointer(&zeroKey[0]), unsafe.Pointer(&zeroValue[0]), 0) + if err != nil { + log.Error("eBPF could not init counters to zero: %v", err) + return err + } + } + ebpfCache = NewEbpfCache() + + lock.Lock() + //determine host byte order + buf := [2]byte{} + *(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD) + switch buf { + case [2]byte{0xCD, 0xAB}: + hostByteOrder = binary.LittleEndian + case [2]byte{0xAB, 0xCD}: + hostByteOrder = binary.BigEndian + default: + log.Error("Could not determine host byte order.") + } + lock.Unlock() + + ebpfMaps = map[string]*ebpfMapsForProto{ + "tcp": { + counterMap: m.Map("tcpcounter"), + bpfmap: m.Map("tcpMap")}, + "tcp6": { + counterMap: m.Map("tcpv6counter"), + bpfmap: m.Map("tcpv6Map")}, + "udp": { + counterMap: m.Map("udpcounter"), + bpfmap: m.Map("udpMap")}, + "udp6": { + counterMap: m.Map("udpv6counter"), + bpfmap: m.Map("udpv6Map")}, + } + + saveEstablishedConnections(uint8(syscall.AF_INET)) + if core.IPv6Enabled { + saveEstablishedConnections(uint8(syscall.AF_INET6)) + } + + go monitorCache() + go monitorMaps() + go monitorLocalAddresses() + go monitorAlreadyEstablished() + return nil +} + +func saveEstablishedConnections(commDomain uint8) error { + // save already established connections + socketListTCP, err := daemonNetlink.SocketsDump(commDomain, uint8(syscall.IPPROTO_TCP)) + if err != nil { + log.Debug("eBPF could not dump TCP (%d) sockets via netlink: %v", commDomain, err) + return err + } + for _, sock := range socketListTCP { + inode := int((*sock).INode) + pid := procmon.GetPIDFromINode(inode, fmt.Sprint(inode, + (*sock).ID.Source, (*sock).ID.SourcePort, (*sock).ID.Destination, (*sock).ID.DestinationPort)) + alreadyEstablished.Lock() + alreadyEstablished.TCP[sock] = pid + alreadyEstablished.Unlock() + } + + return nil +} + +// Stop stops monitoring connections using kprobes +func Stop() { + lock.Lock() + stop = true + lock.Unlock() + if m != nil { + m.Close() + } + ebpfCache.clear() +} + +func isStopped() bool { + lock.RLock() + defer lock.RUnlock() + + return stop +} + +//make bpf() syscall with bpf_lookup prepared by the caller +func makeBpfSyscall(bpf_lookup *bpf_lookup_elem_t) uintptr { + BPF_MAP_LOOKUP_ELEM := 1 //cmd number + syscall_BPF := 321 //syscall number + sizeOfStruct := 24 //sizeof bpf_lookup_elem_t struct + + r1, _, _ := syscall.Syscall(uintptr(syscall_BPF), uintptr(BPF_MAP_LOOKUP_ELEM), + uintptr(unsafe.Pointer(bpf_lookup)), uintptr(sizeOfStruct)) + return r1 +} diff --git a/daemon/procmon/ebpf/find.go b/daemon/procmon/ebpf/find.go new file mode 100644 index 0000000..be30f30 --- /dev/null +++ b/daemon/procmon/ebpf/find.go @@ -0,0 +1,171 @@ +package ebpf + +import ( + "encoding/binary" + "fmt" + "net" + "unsafe" + + daemonNetlink "github.com/evilsocket/opensnitch/daemon/netlink" +) + +// we need to manually remove old connections from a bpf map + +// GetPid looks up process pid in a bpf map. If not found there, then it searches +// already-established TCP connections. +func GetPid(proto string, srcPort uint, srcIP net.IP, dstIP net.IP, dstPort uint) (int, int, error) { + if hostByteOrder == nil { + return -1, -1, fmt.Errorf("eBPF monitoring method not initialized yet") + } + + if pid, uid := getPidFromEbpf(proto, srcPort, srcIP, dstIP, dstPort); pid != -1 { + return pid, uid, nil + } + //check if it comes from already established TCP + if proto == "tcp" || proto == "tcp6" { + if pid, uid, err := findInAlreadyEstablishedTCP(proto, srcPort, srcIP, dstIP, dstPort); err == nil { + return pid, uid, nil + } + } + //using netlink.GetSocketInfo to check if UID is 0 (in-kernel connection) + if uid, _ := daemonNetlink.GetSocketInfo(proto, srcIP, srcPort, dstIP, dstPort); uid == 0 { + return -100, -100, nil + } + if !findAddressInLocalAddresses(srcIP) { + // systemd-resolved sometimes makes a TCP Fast Open connection to a DNS server (8.8.8.8 on my machine) + // and we get a packet here with **source** (not detination!!!) IP 8.8.8.8 + // Maybe it's an in-kernel response with spoofed IP because wireshark does not show neither + // resolved's TCP Fast Open packet, nor the response + // Until this is better understood, we simply do not allow this machine to make connections with + // arbitrary source IPs + return -1, -1, fmt.Errorf("eBPF packet with unknown source IP: %s", srcIP) + } + return -1, -1, nil +} + +// getPidFromEbpf looks up a connection in bpf map and returns PID if found +// the lookup keys and values are defined in opensnitch.c , e.g. +// +// struct tcp_key_t { +// u16 sport; +// u32 daddr; +// u16 dport; +// u32 saddr; +// }__attribute__((packed)); + +// struct tcp_value_t{ +// u64 pid; +// u64 uid; +// u64 counter; +// }__attribute__((packed));; + +func getPidFromEbpf(proto string, srcPort uint, srcIP net.IP, dstIP net.IP, dstPort uint) (pid int, uid int) { + if hostByteOrder == nil { + return -1, -1 + } + // Some connections, like broadcasts, are only seen in eBPF once, + // but some applications send 1 connection per network interface. + // If we delete the eBPF entry the first time we see it, we won't find + // the connection the next times. + delItemIfFound := true + + var key []byte + var value []byte + var isIP4 bool = (proto == "tcp") || (proto == "udp") || (proto == "udplite") + + if isIP4 { + key = make([]byte, 12) + value = make([]byte, 24) + copy(key[2:6], dstIP) + binary.BigEndian.PutUint16(key[6:8], uint16(dstPort)) + copy(key[8:12], srcIP) + } else { // IPv6 + key = make([]byte, 36) + value = make([]byte, 24) + copy(key[2:18], dstIP) + binary.BigEndian.PutUint16(key[18:20], uint16(dstPort)) + copy(key[20:36], srcIP) + } + hostByteOrder.PutUint16(key[0:2], uint16(srcPort)) + + k := fmt.Sprint(proto, srcPort, srcIP.String(), dstIP.String(), dstPort) + cacheItem, isInCache := ebpfCache.isInCache(k) + if isInCache { + deleteEbpfEntry(proto, unsafe.Pointer(&key[0])) + return cacheItem.Pid, cacheItem.UID + } + + err := m.LookupElement(ebpfMaps[proto].bpfmap, unsafe.Pointer(&key[0]), unsafe.Pointer(&value[0])) + if err != nil { + // key not found + // sometimes srcIP is 0.0.0.0. Happens especially with UDP sendto() + // for example: 57621:10.0.3.1 -> 10.0.3.255:57621 , reported as: 0.0.0.0 -> 10.0.3.255 + if isIP4 { + zeroes := make([]byte, 4) + copy(key[8:12], zeroes) + } else { + zeroes := make([]byte, 16) + copy(key[20:36], zeroes) + } + err = m.LookupElement(ebpfMaps[proto].bpfmap, unsafe.Pointer(&key[0]), unsafe.Pointer(&value[0])) + if err == nil { + delItemIfFound = false + } + } + if err != nil && proto == "udp" && srcIP.String() == dstIP.String() { + // very rarely I see this connection. It has srcIP and dstIP == 0.0.0.0 in ebpf map + // it is a localhost to localhost connection + // srcIP was already set to 0, set dstIP to zero also + // TODO try to reproduce it and look for srcIP/dstIP in other kernel structures + zeroes := make([]byte, 4) + copy(key[2:6], zeroes) + err = m.LookupElement(ebpfMaps[proto].bpfmap, unsafe.Pointer(&key[0]), unsafe.Pointer(&value[0])) + } + + if err != nil { + // key not found in bpf maps + return -1, -1 + } + pid = int(hostByteOrder.Uint32(value[0:4])) + uid = int(hostByteOrder.Uint32(value[8:12])) + + ebpfCache.addNewItem(k, key, pid, uid) + if delItemIfFound { + deleteEbpfEntry(proto, unsafe.Pointer(&key[0])) + } + return pid, uid +} + +// FindInAlreadyEstablishedTCP searches those TCP connections which were already established at the time +// when opensnitch started +func findInAlreadyEstablishedTCP(proto string, srcPort uint, srcIP net.IP, dstIP net.IP, dstPort uint) (int, int, error) { + alreadyEstablished.RLock() + defer alreadyEstablished.RUnlock() + + var _alreadyEstablished map[*daemonNetlink.Socket]int + if proto == "tcp" { + _alreadyEstablished = alreadyEstablished.TCP + } else if proto == "tcp6" { + _alreadyEstablished = alreadyEstablished.TCPv6 + } + + for sock, v := range _alreadyEstablished { + if (*sock).ID.SourcePort == uint16(srcPort) && (*sock).ID.Source.Equal(srcIP) && + (*sock).ID.Destination.Equal(dstIP) && (*sock).ID.DestinationPort == uint16(dstPort) { + return v, int((*sock).UID), nil + } + } + return -1, -1, fmt.Errorf("eBPF inode not found") +} + +//returns true if addr is in the list of this machine's addresses +func findAddressInLocalAddresses(addr net.IP) bool { + lock.Lock() + defer lock.Unlock() + for _, a := range localAddresses { + if addr.String() == a.String() { + return true + } + } + return false +} diff --git a/daemon/procmon/ebpf/monitor.go b/daemon/procmon/ebpf/monitor.go new file mode 100644 index 0000000..e2898b4 --- /dev/null +++ b/daemon/procmon/ebpf/monitor.go @@ -0,0 +1,127 @@ +package ebpf + +import ( + "syscall" + "time" + + "github.com/evilsocket/opensnitch/daemon/core" + "github.com/evilsocket/opensnitch/daemon/log" + daemonNetlink "github.com/evilsocket/opensnitch/daemon/netlink" + "github.com/vishvananda/netlink" +) + +// we need to manually remove old connections from a bpf map +// since when a bpf map is full it doesn't allow any more insertions +func monitorMaps() { + for { + if isStopped() { + return + } + time.Sleep(time.Second * 5) + for name := range ebpfMaps { + // using a pointer to the map doesn't delete the items. + // bpftool still counts them. + if items := getItems(name, name == "tcp6" || name == "udp6"); items > 500 { + deleted := deleteOldItems(name, name == "tcp6" || name == "udp6", items/2) + log.Debug("[ebpf] old items deleted: %d", deleted) + } + } + } +} + +func monitorCache() { + for { + select { + case <-ebpfCacheTicker.C: + if isStopped() { + return + } + ebpfCache.DeleteOldItems() + } + } +} + +// maintains a list of this machine's local addresses +// TODO: use netlink.AddrSubscribeWithOptions() +func monitorLocalAddresses() { + for { + addr, err := netlink.AddrList(nil, netlink.FAMILY_ALL) + if err != nil { + log.Error("eBPF error looking up this machine's addresses via netlink: %v", err) + continue + } + lock.Lock() + localAddresses = nil + for _, a := range addr { + localAddresses = append(localAddresses, a.IP) + } + lock.Unlock() + time.Sleep(time.Second * 1) + if isStopped() { + return + } + } +} + +// monitorAlreadyEstablished makes sure that when an already-established connection is closed +// it will be removed from alreadyEstablished. If we don't do this and keep the alreadyEstablished entry forever, +// then after the genuine process quits,a malicious process may reuse PID-srcPort-srcIP-dstPort-dstIP +func monitorAlreadyEstablished() { + for { + time.Sleep(time.Second * 1) + if isStopped() { + return + } + socketListTCP, err := daemonNetlink.SocketsDump(uint8(syscall.AF_INET), uint8(syscall.IPPROTO_TCP)) + if err != nil { + log.Debug("eBPF error in dumping TCP sockets via netlink") + continue + } + alreadyEstablished.Lock() + for aesock := range alreadyEstablished.TCP { + found := false + for _, sock := range socketListTCP { + if socketsAreEqual(aesock, sock) { + found = true + break + } + } + if !found { + delete(alreadyEstablished.TCP, aesock) + } + } + alreadyEstablished.Unlock() + + if core.IPv6Enabled { + socketListTCPv6, err := daemonNetlink.SocketsDump(uint8(syscall.AF_INET6), uint8(syscall.IPPROTO_TCP)) + if err != nil { + log.Debug("eBPF error in dumping TCPv6 sockets via netlink: %s", err) + continue + } + alreadyEstablished.Lock() + for aesock := range alreadyEstablished.TCPv6 { + found := false + for _, sock := range socketListTCPv6 { + if socketsAreEqual(aesock, sock) { + found = true + break + } + } + if !found { + delete(alreadyEstablished.TCPv6, aesock) + } + } + alreadyEstablished.Unlock() + } + } +} + +func socketsAreEqual(aSocket, bSocket *daemonNetlink.Socket) bool { + return ((*aSocket).INode == (*bSocket).INode && + //inodes are unique enough, so the matches below will never have to be checked + (*aSocket).ID.SourcePort == (*bSocket).ID.SourcePort && + (*aSocket).ID.Source.Equal((*bSocket).ID.Source) && + (*aSocket).ID.Destination.Equal((*bSocket).ID.Destination) && + (*aSocket).ID.DestinationPort == (*bSocket).ID.DestinationPort && + (*aSocket).UID == (*bSocket).UID) +} diff --git a/daemon/procmon/ebpf/utils.go b/daemon/procmon/ebpf/utils.go new file mode 100644 index 0000000..c83f895 --- /dev/null +++ b/daemon/procmon/ebpf/utils.go @@ -0,0 +1,124 @@ +package ebpf + +import ( + "fmt" + "unsafe" + + "github.com/evilsocket/opensnitch/daemon/core" + "github.com/evilsocket/opensnitch/daemon/log" +) + +func mountDebugFS() error { + debugfsPath := "/sys/kernel/debug/" + kprobesPath := fmt.Sprint(debugfsPath, "tracing/kprobe_events") + if core.Exists(kprobesPath) == false { + if _, err := core.Exec("mount", []string{"-t", "debugfs", "none", debugfsPath}); err != nil { + log.Warning("eBPF debugfs error: %s", err) + return err + } + } + + return nil +} + +func deleteEbpfEntry(proto string, key unsafe.Pointer) bool { + if err := m.DeleteElement(ebpfMaps[proto].bpfmap, key); err != nil { + return false + } + return true +} + +func getItems(proto string, isIPv6 bool) (items uint) { + isDup := make(map[string]uint8) + var lookupKey []byte + var nextKey []byte + var value []byte + if !isIPv6 { + lookupKey = make([]byte, 12) + nextKey = make([]byte, 12) + } else { + lookupKey = make([]byte, 36) + nextKey = make([]byte, 36) + } + value = make([]byte, 24) + firstrun := true + + for { + ok, err := m.LookupNextElement(ebpfMaps[proto].bpfmap, unsafe.Pointer(&lookupKey[0]), + unsafe.Pointer(&nextKey[0]), unsafe.Pointer(&value[0])) + if !ok || err != nil { //reached end of map + log.Debug("[ebpf] %s map: %d active items", proto, items) + return + } + if firstrun { + // on first run lookupKey is a dummy, nothing to delete + firstrun = false + copy(lookupKey, nextKey) + continue + } + if counter, duped := isDup[string(lookupKey)]; duped && counter > 1 { + deleteEbpfEntry(proto, unsafe.Pointer(&lookupKey[0])) + continue + } + isDup[string(lookupKey)]++ + copy(lookupKey, nextKey) + items++ + } + + return items +} + +// deleteOldItems deletes maps' elements in order to keep them below maximum capacity. +// If ebpf maps are full they don't allow any more insertions, ending up lossing events. +func deleteOldItems(proto string, isIPv6 bool, maxToDelete uint) (deleted uint) { + isDup := make(map[string]uint8) + var lookupKey []byte + var nextKey []byte + var value []byte + if !isIPv6 { + lookupKey = make([]byte, 12) + nextKey = make([]byte, 12) + } else { + lookupKey = make([]byte, 36) + nextKey = make([]byte, 36) + } + value = make([]byte, 24) + firstrun := true + i := uint(0) + + for { + i++ + if i > maxToDelete { + return + } + ok, err := m.LookupNextElement(ebpfMaps[proto].bpfmap, unsafe.Pointer(&lookupKey[0]), + unsafe.Pointer(&nextKey[0]), unsafe.Pointer(&value[0])) + if !ok || err != nil { //reached end of map + return + } + if counter, duped := isDup[string(lookupKey)]; duped && counter > 1 { + if deleteEbpfEntry(proto, unsafe.Pointer(&lookupKey[0])) { + deleted++ + copy(lookupKey, nextKey) + continue + } + return + } + + if firstrun { + // on first run lookupKey is a dummy, nothing to delete + firstrun = false + copy(lookupKey, nextKey) + continue + } + + if !deleteEbpfEntry(proto, unsafe.Pointer(&lookupKey[0])) { + return + } + deleted++ + isDup[string(lookupKey)]++ + copy(lookupKey, nextKey) + } + + return +} diff --git a/daemon/procmon/find.go b/daemon/procmon/find.go new file mode 100644 index 0000000..8675b60 --- /dev/null +++ b/daemon/procmon/find.go @@ -0,0 +1,108 @@ +package procmon + +import ( + "fmt" + "os" + "sort" + "strconv" +) + +func sortPidsByTime(fdList []os.FileInfo) []os.FileInfo { + sort.Slice(fdList, func(i, j int) bool { + t := fdList[i].ModTime().UnixNano() + u := fdList[j].ModTime().UnixNano() + return t > u + }) + return fdList +} + +// inodeFound searches for the given inode in /proc/<pid>/fd/ or +// /proc/<pid>/task/<tid>/fd/ and gets the symbolink link it points to, +// in order to compare it against the given inode. +// +// If the inode is found, the cache is updated ans sorted. +func inodeFound(pidsPath, expect, inodeKey string, inode, pid int) bool { + fdPath := fmt.Sprint(pidsPath, pid, "/fd/") + fdList := lookupPidDescriptors(fdPath, pid) + if fdList == nil { + return false + } + + for idx := 0; idx < len(fdList); idx++ { + descLink := fmt.Sprint(fdPath, fdList[idx]) + if link, err := os.Readlink(descLink); err == nil && link == expect { + inodesCache.add(inodeKey, descLink, pid) + pidsCache.add(fdPath, fdList, pid) + return true + } + } + + return false +} + +// lookupPidInProc searches for an inode in /proc. +// First it gets the running PIDs and obtains the opened sockets. +// TODO: If the inode is not found, search again in the task/threads +// of every PID (costly). +func lookupPidInProc(pidsPath, expect, inodeKey string, inode int) int { + pidList := getProcPids(pidsPath) + for _, pid := range pidList { + if inodeFound(pidsPath, expect, inodeKey, inode, pid) { + return pid + } + } + return -1 +} + +// lookupPidDescriptors returns the list of descriptors inside +// /proc/<pid>/fd/ +// TODO: search in /proc/<pid>/task/<tid>/fd/ . +func lookupPidDescriptors(fdPath string, pid int) []string { + f, err := os.Open(fdPath) + if err != nil { + return nil + } + // This is where most of the time is wasted when looking for PIDs. + // long running processes like firefox/chrome tend to have a lot of descriptor + // references that points to non existent files on disk, but that remains in + // memory (those with " (deleted)"). + // This causes to have to iterate over 300 to 700 items, that are not sockets. + fdList, err := f.Readdir(-1) + f.Close() + if err != nil { + return nil + } + fdList = sortPidsByTime(fdList) + + s := make([]string, len(fdList)) + for n, f := range fdList { + s[n] = f.Name() + } + + return s +} + +// getProcPids returns the list of running PIDs, /proc or /proc/<pid>/task/ . +func getProcPids(pidsPath string) (pidList []int) { + f, err := os.Open(pidsPath) + if err != nil { + return pidList + } + ls, err := f.Readdir(-1) + f.Close() + if err != nil { + return pidList + } + ls = sortPidsByTime(ls) + + for _, f := range ls { + if f.IsDir() == false { + continue + } + if pid, err := strconv.Atoi(f.Name()); err == nil { + pidList = append(pidList, []int{pid}...) + } + } + + return pidList +} diff --git a/daemon/procmon/find_test.go b/daemon/procmon/find_test.go new file mode 100644 index 0000000..9588de8 --- /dev/null +++ b/daemon/procmon/find_test.go @@ -0,0 +1,42 @@ +package procmon + +import ( + "fmt" + "testing" +) + +func TestGetProcPids(t *testing.T) { + pids := getProcPids("/proc") + + if len(pids) == 0 { + t.Error("getProcPids() should not be 0", pids) + } +} + +func TestLookupPidDescriptors(t *testing.T) { + pidsFd := lookupPidDescriptors(fmt.Sprint("/proc/", myPid, "/fd/"), myPid) + if len(pidsFd) == 0 { + t.Error("getProcPids() should not be 0", pidsFd) + } +} + +func TestLookupPidInProc(t *testing.T) { + // we expect that the inode 1 points to /dev/null + expect := "/dev/null" + foundPid := lookupPidInProc("/proc/", expect, "", myPid) + if foundPid == -1 { + t.Error("lookupPidInProc() should not return -1") + } +} + +func BenchmarkGetProcs(b *testing.B) { + for i := 0; i < b.N; i++ { + getProcPids("/proc") + } +} + +func BenchmarkLookupPidDescriptors(b *testing.B) { + for i := 0; i < b.N; i++ { + lookupPidDescriptors(fmt.Sprint("/proc/", myPid, "/fd/"), myPid) + } +} diff --git a/daemon/procmon/monitor/init.go b/daemon/procmon/monitor/init.go new file mode 100644 index 0000000..4bad752 --- /dev/null +++ b/daemon/procmon/monitor/init.go @@ -0,0 +1,79 @@ +package monitor + +import ( + "net" + + "github.com/evilsocket/opensnitch/daemon/log" + "github.com/evilsocket/opensnitch/daemon/procmon" + "github.com/evilsocket/opensnitch/daemon/procmon/audit" + "github.com/evilsocket/opensnitch/daemon/procmon/ebpf" +) + +var ( + cacheMonitorsRunning = false +) + +// ReconfigureMonitorMethod configures a new method for parsing connections. +func ReconfigureMonitorMethod(newMonitorMethod string) error { + + if procmon.GetMonitorMethod() == newMonitorMethod { + return nil + } + + oldMethod := procmon.GetMonitorMethod() + End() + procmon.SetMonitorMethod(newMonitorMethod) + // if the new monitor method fails to start, rollback the change and exit + // without saving the configuration. Otherwise we can end up with the wrong + // monitor method configured and saved to file. + if err := Init(); err != nil { + procmon.SetMonitorMethod(oldMethod) + return err + } + + return nil +} + +// End stops the way of parsing new connections. +func End() { + if procmon.MethodIsAudit() { + audit.Stop() + } else if procmon.MethodIsEbpf() { + ebpf.Stop() + } +} + +// Init starts parsing connections using the method specified. +func Init() (err error) { + if cacheMonitorsRunning == false { + go procmon.MonitorActivePids() + go procmon.CacheCleanerTask() + cacheMonitorsRunning = true + } + + if procmon.MethodIsEbpf() { + err = ebpf.Start() + if err == nil { + log.Info("Process monitor method ebpf") + return nil + } + // we need to stop this method even if it has failed to start, in order to clean up the kprobes + // It helps with the error "cannot write...kprobe_events: file exists". + ebpf.Stop() + log.Warning("error starting ebpf monitor method: %v", err) + } else if procmon.MethodIsAudit() { + var auditConn net.Conn + auditConn, err = audit.Start() + if err == nil { + log.Info("Process monitor method audit") + go audit.Reader(auditConn, (chan<- audit.Event)(audit.EventChan)) + return nil + } + log.Warning("error starting audit monitor method: %v", err) + } + + // if any of the above methods have failed, fallback to proc + log.Info("Process monitor method /proc") + procmon.SetMonitorMethod(procmon.MethodProc) + return err +} diff --git a/daemon/procmon/parse.go b/daemon/procmon/parse.go new file mode 100644 index 0000000..b0bb824 --- /dev/null +++ b/daemon/procmon/parse.go @@ -0,0 +1,134 @@ +package procmon + +import ( + "fmt" + "os" + "time" + + "github.com/evilsocket/opensnitch/daemon/core" + "github.com/evilsocket/opensnitch/daemon/log" + "github.com/evilsocket/opensnitch/daemon/procmon/audit" +) + +func getPIDFromAuditEvents(inode int, inodeKey string, expect string) (int, int) { + audit.Lock.RLock() + defer audit.Lock.RUnlock() + + auditEvents := audit.GetEvents() + for n := 0; n < len(auditEvents); n++ { + pid := auditEvents[n].Pid + if inodeFound("/proc/", expect, inodeKey, inode, pid) { + return pid, n + } + } + for n := 0; n < len(auditEvents); n++ { + ppid := auditEvents[n].PPid + if inodeFound("/proc/", expect, inodeKey, inode, ppid) { + return ppid, n + } + } + return -1, -1 +} + +// GetPIDFromINode tries to get the PID from a socket inode following these steps: +// 1. Get the PID from the cache of Inodes. +// 2. Get the PID from the cache of PIDs. +// 3. Look for the PID using one of these methods: +// - audit: listening for socket creation from auditd. +// - proc: search /proc +// +// If the PID is not found by one of the 2 first methods, it'll try it using /proc. +func GetPIDFromINode(inode int, inodeKey string) int { + found := -1 + if inode <= 0 { + return found + } + start := time.Now() + + expect := fmt.Sprintf("socket:[%d]", inode) + if cachedPidInode := inodesCache.getPid(inodeKey); cachedPidInode != -1 { + log.Debug("Inode found in cache: %v %v %v %v", time.Since(start), inodesCache.getPid(inodeKey), inode, inodeKey) + return cachedPidInode + } + + cachedPid, pos := pidsCache.getPid(inode, inodeKey, expect) + if cachedPid != -1 { + log.Debug("Socket found in known pids %v, pid: %d, inode: %d, pos: %d, pids in cache: %d", time.Since(start), cachedPid, inode, pos, pidsCache.countItems()) + pidsCache.sort(cachedPid) + inodesCache.add(inodeKey, "", cachedPid) + return cachedPid + } + + if MethodIsAudit() { + if aPid, pos := getPIDFromAuditEvents(inode, inodeKey, expect); aPid != -1 { + log.Debug("PID found via audit events: %v, position: %d", time.Since(start), pos) + return aPid + } + } + if found == -1 || methodIsProc() { + found = lookupPidInProc("/proc/", expect, inodeKey, inode) + } + log.Debug("new pid lookup took (%d): %v", found, time.Since(start)) + + return found +} + +// FindProcess checks if a process exists given a PID. +// If it exists in /proc, a new Process{} object is returned with the details +// to identify a process (cmdline, name, environment variables, etc). +func FindProcess(pid int, interceptUnknown bool) *Process { + if interceptUnknown && pid < 0 { + return NewProcess(0, "") + } + + if proc := findProcessInActivePidsCache(uint64(pid)); proc != nil { + return proc + } + + if MethodIsAudit() { + if aevent := audit.GetEventByPid(pid); aevent != nil { + audit.Lock.RLock() + proc := NewProcess(pid, aevent.ProcPath) + proc.readCmdline() + proc.setCwd(aevent.ProcDir) + audit.Lock.RUnlock() + // if the proc dir contains non alhpa-numeric chars the field is empty + if proc.CWD == "" { + proc.readCwd() + } + proc.readEnv() + proc.cleanPath() + + addToActivePidsCache(uint64(pid), proc) + return proc + } + } + // if the PID dir doesn't exist, the process may have exited or be a kernel connection + // XXX: can a kernel connection exist without an entry in ProcFS? + if core.Exists(fmt.Sprint("/proc/", pid)) == false { + log.Debug("PID can't be read /proc/ %d", pid) + return nil + } + + linkName := fmt.Sprint("/proc/", pid, "/exe") + link, err := os.Readlink(linkName) + proc := NewProcess(pid, link) + proc.readCmdline() + proc.readCwd() + proc.readEnv() + proc.cleanPath() + + if len(proc.Args) == 0 { + proc.readComm() + proc.Args = make([]string, 0) + proc.Args = append(proc.Args, proc.Comm) + } + + // If the link to the binary can't be read, the PID may be of a kernel task + if err != nil || proc.Path == "" { + proc.Path = "Kernel connection" + } + + addToActivePidsCache(uint64(pid), proc) + return proc +} diff --git a/daemon/procmon/process.go b/daemon/procmon/process.go new file mode 100644 index 0000000..f12c396 --- /dev/null +++ b/daemon/procmon/process.go @@ -0,0 +1,112 @@ +package procmon + +import ( + "sync" + "time" +) + +var ( + cacheMonitorsRunning = false + lock = sync.RWMutex{} + monitorMethod = MethodProc +) + +// monitor method supported types +const ( + MethodProc = "proc" + MethodAudit = "audit" + MethodEbpf = "ebpf" +) + +// man 5 proc; man procfs +type procIOstats struct { + RChar int64 + WChar int64 + SyscallRead int64 + SyscallWrite int64 + ReadBytes int64 + WriteBytes int64 +} + +type procDescriptors struct { + Name string + SymLink string + Size int64 + ModTime time.Time +} + +type procStatm struct { + Size int64 + Resident int64 + Shared int64 + Text int64 + Lib int64 + Data int64 // data + stack + Dt int +} + +// Process holds the details of a process. +type Process struct { + ID int + Comm string + Path string + Args []string + Env map[string]string + CWD string + Descriptors []*procDescriptors + IOStats *procIOstats + Status string + Stat string + Statm *procStatm + Stack string + Maps string +} + +// NewProcess returns a new Process structure. +func NewProcess(pid int, path string) *Process { + return &Process{ + ID: pid, + Path: path, + Args: make([]string, 0), + Env: make(map[string]string), + } +} + +// SetMonitorMethod configures a new method for parsing connections. +func SetMonitorMethod(newMonitorMethod string) { + lock.Lock() + defer lock.Unlock() + + monitorMethod = newMonitorMethod +} + +// GetMonitorMethod configures a new method for parsing connections. +func GetMonitorMethod() string { + lock.Lock() + defer lock.Unlock() + + return monitorMethod +} + +// MethodIsEbpf returns if the process monitor method is eBPF. +func MethodIsEbpf() bool { + lock.RLock() + defer lock.RUnlock() + + return monitorMethod == MethodEbpf +} + +// MethodIsAudit returns if the process monitor method is eBPF. +func MethodIsAudit() bool { + lock.RLock() + defer lock.RUnlock() + + return monitorMethod == MethodAudit +} + +func methodIsProc() bool { + lock.RLock() + defer lock.RUnlock() + + return monitorMethod == MethodProc +} diff --git a/daemon/procmon/process_test.go b/daemon/procmon/process_test.go new file mode 100644 index 0000000..258ac44 --- /dev/null +++ b/daemon/procmon/process_test.go @@ -0,0 +1,135 @@ +package procmon + +import ( + "os" + "testing" +) + +var ( + myPid = os.Getpid() + proc = NewProcess(myPid, "/fake/path") +) + +func TestNewProcess(t *testing.T) { + if proc.ID != myPid { + t.Error("NewProcess PID not equal to ", myPid) + } + if proc.Path != "/fake/path" { + t.Error("NewProcess path not equal to /fake/path") + } +} + +func TestProcPath(t *testing.T) { + if err := proc.readPath(); err != nil { + t.Error("Proc path error:", err) + } + if proc.Path == "/fake/path" { + t.Error("Proc path equal to /fake/path, should be different:", proc.Path) + } +} + +func TestProcCwd(t *testing.T) { + err := proc.readCwd() + + if proc.CWD == "" { + t.Error("Proc readCwd() not read:", err) + } + + proc.setCwd("/home") + if proc.CWD != "/home" { + t.Error("Proc setCwd() should be /home:", proc.CWD) + } +} + +func TestProcCmdline(t *testing.T) { + proc.readCmdline() + + if len(proc.Args) == 0 { + t.Error("Proc Args should not be empty:", proc.Args) + } +} + +func TestProcDescriptors(t *testing.T) { + proc.readDescriptors() + + if len(proc.Descriptors) == 0 { + t.Error("Proc Descriptors should not be empty:", proc.Descriptors) + } +} + +func TestProcEnv(t *testing.T) { + proc.readEnv() + + if len(proc.Env) == 0 { + t.Error("Proc Env should not be empty:", proc.Env) + } +} + +func TestProcIOStats(t *testing.T) { + proc.readIOStats() + + if proc.IOStats.RChar == 0 { + t.Error("Proc.IOStats.RChar should not be 0:", proc.IOStats) + } + if proc.IOStats.WChar == 0 { + t.Error("Proc.IOStats.WChar should not be 0:", proc.IOStats) + } + if proc.IOStats.SyscallRead == 0 { + t.Error("Proc.IOStats.SyscallRead should not be 0:", proc.IOStats) + } + if proc.IOStats.SyscallWrite == 0 { + t.Error("Proc.IOStats.SyscallWrite should not be 0:", proc.IOStats) + } + /*if proc.IOStats.ReadBytes == 0 { + t.Error("Proc.IOStats.ReadBytes should not be 0:", proc.IOStats) + } + if proc.IOStats.WriteBytes == 0 { + t.Error("Proc.IOStats.WriteBytes should not be 0:", proc.IOStats) + }*/ +} + +func TestProcStatus(t *testing.T) { + proc.readStatus() + + if proc.Status == "" { + t.Error("Proc Status should not be empty:", proc) + } + if proc.Stat == "" { + t.Error("Proc Stat should not be empty:", proc) + } + /*if proc.Stack == "" { + t.Error("Proc Stack should not be empty:", proc) + }*/ + if proc.Maps == "" { + t.Error("Proc Maps should not be empty:", proc) + } + if proc.Statm.Size == 0 { + t.Error("Proc Statm Size should not be 0:", proc.Statm) + } + if proc.Statm.Resident == 0 { + t.Error("Proc Statm Resident should not be 0:", proc.Statm) + } + if proc.Statm.Shared == 0 { + t.Error("Proc Statm Shared should not be 0:", proc.Statm) + } + if proc.Statm.Text == 0 { + t.Error("Proc Statm Text should not be 0:", proc.Statm) + } + if proc.Statm.Lib != 0 { + t.Error("Proc Statm Lib should not be 0:", proc.Statm) + } + if proc.Statm.Data == 0 { + t.Error("Proc Statm Data should not be 0:", proc.Statm) + } + if proc.Statm.Dt != 0 { + t.Error("Proc Statm Dt should not be 0:", proc.Statm) + } +} + +func TestProcCleanPath(t *testing.T) { + proc.Path = "/fake/path/binary (deleted)" + proc.cleanPath() + if proc.Path != "/fake/path/binary" { + t.Error("Proc cleanPath() not cleaned:", proc.Path) + } +} diff --git a/daemon/rule/loader.go b/daemon/rule/loader.go new file mode 100644 index 0000000..378d8d4 --- /dev/null +++ b/daemon/rule/loader.go @@ -0,0 +1,418 @@ +package rule + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path" + "path/filepath" + "sort" + "strings" + "sync" + "time" + + "github.com/evilsocket/opensnitch/daemon/conman" + "github.com/evilsocket/opensnitch/daemon/core" + "github.com/evilsocket/opensnitch/daemon/log" + + "github.com/fsnotify/fsnotify" +) + +// Loader is the object that holds the rules loaded from disk, as well as the +// rules watcher. +type Loader struct { + sync.RWMutex + path string + rules map[string]*Rule + rulesKeys []string + watcher *fsnotify.Watcher + liveReload bool + liveReloadRunning bool +} + +// NewLoader loads rules from disk, and watches for changes made to the rules files +// on disk. +func NewLoader(liveReload bool) (*Loader, error) { + watcher, err := fsnotify.NewWatcher() + if err != nil { + return nil, err + } + return &Loader{ + path: "", + rules: make(map[string]*Rule), + liveReload: liveReload, + watcher: watcher, + liveReloadRunning: false, + }, nil +} + +// NumRules returns he number of loaded rules. +func (l *Loader) NumRules() int { + l.RLock() + defer l.RUnlock() + return len(l.rules) +} + +// GetAll returns the loaded rules. +func (l *Loader) GetAll() map[string]*Rule { + l.RLock() + defer l.RUnlock() + return l.rules +} + +// Load loads rules files from disk. +func (l *Loader) Load(path string) error { + if core.Exists(path) == false { + return fmt.Errorf("Path '%s' does not exist", path) + } + + expr := filepath.Join(path, "*.json") + matches, err := filepath.Glob(expr) + if err != nil { + return fmt.Errorf("Error globbing '%s': %s", expr, err) + } + + l.path = path + if len(l.rules) == 0 { + l.rules = make(map[string]*Rule) + } + + for _, fileName := range matches { + log.Debug("Reading rule from %s", fileName) + + if err := l.loadRule(fileName); err != nil { + log.Warning("%s", err) + continue + } + } + + if l.liveReload && l.liveReloadRunning == false { + go l.liveReloadWorker() + } + + return nil +} + +func (l *Loader) loadRule(fileName string) error { + raw, err := ioutil.ReadFile(fileName) + if err != nil { + return fmt.Errorf("Error while reading %s: %s", fileName, err) + } + l.Lock() + defer l.Unlock() + + var r Rule + err = json.Unmarshal(raw, &r) + if err != nil { + return fmt.Errorf("Error parsing rule from %s: %s", fileName, err) + } + raw = nil + + if oldRule, found := l.rules[r.Name]; found { + l.cleanListsRule(oldRule) + } + + if r.Enabled { + if err := r.Operator.Compile(); err != nil { + log.Warning("Operator.Compile() error: %s: %s", err, r.Operator.Data) + return fmt.Errorf("(1) Error compiling rule: %s", err) + } + if r.Operator.Type == List { + for i := 0; i < len(r.Operator.List); i++ { + if err := r.Operator.List[i].Compile(); err != nil { + log.Warning("Operator.Compile() error: %s: ", err) + return fmt.Errorf("(1) Error compiling list rule: %s", err) + } + } + } + } + if oldRule, found := l.rules[r.Name]; found { + l.deleteOldRuleFromDisk(oldRule, &r) + } + + log.Debug("Loaded rule from %s: %s", fileName, r.String()) + l.rules[r.Name] = &r + l.sortRules() + + if l.isTemporary(&r) { + err = l.scheduleTemporaryRule(r) + } + + return nil +} + +// deleteRule deletes a rule from memory if it has been deleted from disk. +// This is only called if fsnotify's Remove event is fired, thus it doesn't +// have to delete temporary rules (!Always). +func (l *Loader) deleteRule(filePath string) { + fileName := filepath.Base(filePath) + ruleName := fileName[:len(fileName)-5] + + l.RLock() + rule, found := l.rules[ruleName] + delRule := found && rule.Duration == Always + l.RUnlock() + if delRule { + l.Delete(ruleName) + } +} + +func (l *Loader) deleteRuleFromDisk(ruleName string) error { + path := fmt.Sprint(l.path, "/", ruleName, ".json") + return os.Remove(path) +} + +// deleteOldRuleFromDisk deletes a rule from disk if the Duration changes +// from Always (saved on disk), to !Always (temporary). +func (l *Loader) deleteOldRuleFromDisk(oldRule, newRule *Rule) { + if oldRule.Duration == Always && newRule.Duration != Always { + if err := l.deleteRuleFromDisk(oldRule.Name); err != nil { + log.Error("Error deleting old rule from disk: %s", oldRule.Name) + } + } +} + +// cleanListsRule erases the list of domains of an Operator of type Lists +func (l *Loader) cleanListsRule(oldRule *Rule) { + if oldRule.Operator.Type == Lists { + oldRule.Operator.StopMonitoringLists() + } else if oldRule.Operator.Type == List { + for i := 0; i < len(oldRule.Operator.List); i++ { + if oldRule.Operator.List[i].Type == Lists { + oldRule.Operator.List[i].StopMonitoringLists() + break + } + } + } +} + +func (l *Loader) liveReloadWorker() { + l.liveReloadRunning = true + + log.Debug("Rules watcher started on path %s ...", l.path) + if err := l.watcher.Add(l.path); err != nil { + log.Error("Could not watch path: %s", err) + l.liveReloadRunning = false + return + } + + for { + select { + case event := <-l.watcher.Events: + // a new rule json file has been created or updated + if event.Op&fsnotify.Write == fsnotify.Write { + if strings.HasSuffix(event.Name, ".json") { + log.Important("Ruleset changed due to %s, reloading ...", path.Base(event.Name)) + if err := l.loadRule(event.Name); err != nil { + log.Warning("%s", err) + } + } + } else if event.Op&fsnotify.Remove == fsnotify.Remove { + if strings.HasSuffix(event.Name, ".json") { + log.Important("Rule deleted %s", path.Base(event.Name)) + // we only need to delete from memory rules of type Always, + // because the Remove event is of a file, i.e.: Duration == Always + l.deleteRule(event.Name) + } + } + case err := <-l.watcher.Errors: + log.Error("File system watcher error: %s", err) + } + } +} + +func (l *Loader) isTemporary(r *Rule) bool { + return r.Duration != Restart && r.Duration != Always && r.Duration != Once +} + +func (l *Loader) isUniqueName(name string) bool { + _, found := l.rules[name] + return !found +} + +func (l *Loader) setUniqueName(rule *Rule) { + l.Lock() + defer l.Unlock() + + idx := 1 + base := rule.Name + for l.isUniqueName(rule.Name) == false { + idx++ + rule.Name = fmt.Sprintf("%s-%d", base, idx) + } +} + +func (l *Loader) sortRules() { + l.rulesKeys = make([]string, 0, len(l.rules)) + for k := range l.rules { + l.rulesKeys = append(l.rulesKeys, k) + } + sort.Strings(l.rulesKeys) +} + +func (l *Loader) addUserRule(rule *Rule) { + if rule.Duration == Once { + return + } + + l.setUniqueName(rule) + l.replaceUserRule(rule) +} + +func (l *Loader) replaceUserRule(rule *Rule) (err error) { + l.Lock() + oldRule, found := l.rules[rule.Name] + l.Unlock() + + if found { + // If the rule has changed from Always (saved on disk) to !Always (temporary), + // we need to delete the rule from disk and keep it in memory. + l.deleteOldRuleFromDisk(oldRule, rule) + + // delete loaded lists, if this is a rule of type Lists + l.cleanListsRule(oldRule) + } + + if rule.Enabled { + if err := rule.Operator.Compile(); err != nil { + log.Warning("Operator.Compile() error: %s: %s", err, rule.Operator.Data) + return fmt.Errorf("(2) Error compiling rule: %s", err) + } + + if rule.Operator.Type == List { + // TODO: use List protobuf object instead of un/marshalling to/from json + if err = json.Unmarshal([]byte(rule.Operator.Data), &rule.Operator.List); err != nil { + return fmt.Errorf("Error loading rule of type list: %s", err) + } + + for i := 0; i < len(rule.Operator.List); i++ { + if err := rule.Operator.List[i].Compile(); err != nil { + log.Warning("Operator.Compile() error: %s: ", err) + return fmt.Errorf("(2) Error compiling list rule: %s", err) + } + } + } + } + l.Lock() + l.rules[rule.Name] = rule + l.sortRules() + l.Unlock() + + if l.isTemporary(rule) { + err = l.scheduleTemporaryRule(*rule) + } + + return err +} + +func (l *Loader) scheduleTemporaryRule(rule Rule) error { + tTime, err := time.ParseDuration(string(rule.Duration)) + if err != nil { + return err + } + + time.AfterFunc(tTime, func() { + l.Lock() + defer l.Unlock() + + log.Info("Temporary rule expired: %s - %s", rule.Name, rule.Duration) + if newRule, found := l.rules[rule.Name]; found { + if newRule.Duration != rule.Duration { + log.Debug("%s temporary rule expired, but has new Duration, old: %s, new: %s", rule.Name, rule.Duration, newRule.Duration) + return + } + delete(l.rules, rule.Name) + l.sortRules() + } + }) + return nil +} + +// Add adds a rule to the list of rules, and optionally saves it to disk. +func (l *Loader) Add(rule *Rule, saveToDisk bool) error { + l.addUserRule(rule) + if saveToDisk { + fileName := filepath.Join(l.path, fmt.Sprintf("%s.json", rule.Name)) + return l.Save(rule, fileName) + } + return nil +} + +// Replace adds a rule to the list of rules, and optionally saves it to disk. +func (l *Loader) Replace(rule *Rule, saveToDisk bool) error { + if err := l.replaceUserRule(rule); err != nil { + return err + } + if saveToDisk { + l.Lock() + defer l.Unlock() + + fileName := filepath.Join(l.path, fmt.Sprintf("%s.json", rule.Name)) + return l.Save(rule, fileName) + } + return nil +} + +// Save a rule to disk. +func (l *Loader) Save(rule *Rule, path string) error { + rule.Updated = time.Now() + raw, err := json.MarshalIndent(rule, "", " ") + if err != nil { + return fmt.Errorf("Error while saving rule %s to %s: %s", rule, path, err) + } + + if err = ioutil.WriteFile(path, raw, 0644); err != nil { + return fmt.Errorf("Error while saving rule %s to %s: %s", rule, path, err) + } + + return nil +} + +// Delete deletes a rule from the list by name. +// If the duration is Always (i.e: saved on disk), it'll attempt to delete +// it from disk. +func (l *Loader) Delete(ruleName string) error { + l.Lock() + defer l.Unlock() + + rule := l.rules[ruleName] + if rule == nil { + return nil + } + l.cleanListsRule(rule) + + delete(l.rules, ruleName) + l.sortRules() + + if rule.Duration != Always { + return nil + } + + log.Info("Delete() rule: %s", rule) + return l.deleteRuleFromDisk(ruleName) +} + +// FindFirstMatch will try match the connection against the existing rule set. +func (l *Loader) FindFirstMatch(con *conman.Connection) (match *Rule) { + l.RLock() + defer l.RUnlock() + + for _, idx := range l.rulesKeys { + rule, _ := l.rules[idx] + if rule.Enabled == false { + continue + } + if rule.Match(con) { + // We have a match. + // Save the rule in order to don't ask the user to take action, + // and keep iterating until a Deny or a Priority rule appears. + match = rule + if rule.Action == Reject || rule.Action == Deny || rule.Precedence == true { + return rule + } + } + } + + return match +} diff --git a/daemon/rule/loader_test.go b/daemon/rule/loader_test.go new file mode 100644 index 0000000..29fa796 --- /dev/null +++ b/daemon/rule/loader_test.go @@ -0,0 +1,275 @@ +package rule + +import ( + "io" + "math/rand" + "os" + "testing" + "time" +) + +var tmpDir string + +func TestMain(m *testing.M) { + tmpDir = "/tmp/ostest_" + randString() + os.Mkdir(tmpDir, 0777) + defer os.RemoveAll(tmpDir) + os.Exit(m.Run()) +} + +func TestRuleLoader(t *testing.T) { + t.Parallel() + t.Log("Test rules loader") + + var list []Operator + dur1s := Duration("1s") + dummyOper, _ := NewOperator(Simple, false, OpTrue, "", list) + dummyOper.Compile() + inMem1sRule := Create("000-xxx-name", true, false, Allow, dur1s, dummyOper) + inMemUntilRestartRule := Create("000-aaa-name", true, false, Allow, Restart, dummyOper) + + l, err := NewLoader(false) + if err != nil { + t.Fail() + } + if err = l.Load("/non/existent/path/"); err == nil { + t.Error("non existent path test: err should not be nil") + } + + if err = l.Load("testdata/"); err != nil { + t.Error("Error loading test rules: ", err) + } + + testNumRules(t, l, 2) + + if err = l.Add(inMem1sRule, false); err != nil { + t.Error("Error adding temporary rule") + } + testNumRules(t, l, 3) + + // test auto deletion of temporary rule + time.Sleep(time.Second * 2) + testNumRules(t, l, 2) + + if err = l.Add(inMemUntilRestartRule, false); err != nil { + t.Error("Error adding temporary rule (2)") + } + testNumRules(t, l, 3) + testRulesOrder(t, l) + testSortRules(t, l) + testFindMatch(t, l) + testFindEnabled(t, l) + testDurationChange(t, l) +} + +func TestRuleLoaderInvalidRegexp(t *testing.T) { + t.Parallel() + t.Log("Test rules loader: invalid regexp") + + l, err := NewLoader(true) + if err != nil { + t.Fail() + } + t.Run("loadRule() from disk test (simple)", func(t *testing.T) { + if err := l.loadRule("testdata/invalid-regexp.json"); err == nil { + t.Error("invalid regexp rule loaded: loadRule()") + } + }) + + t.Run("loadRule() from disk test (list)", func(t *testing.T) { + if err := l.loadRule("testdata/invalid-regexp-list.json"); err == nil { + t.Error("invalid regexp rule loaded: loadRule()") + } + }) + + var list []Operator + dur30m := Duration("30m") + opListData := `[{"type": "regexp", "operand": "process.path", "sensitive": false, "data": "^(/di(rmngr)$"}, {"type": "simple", "operand": "dest.port", "data": "53", "sensitive": false}]` + invalidRegexpOp, _ := NewOperator(List, false, OpList, opListData, list) + invalidRegexpRule := Create("invalid-regexp", true, false, Allow, dur30m, invalidRegexpOp) + + t.Run("replaceUserRule() test list", func(t *testing.T) { + if err := l.replaceUserRule(invalidRegexpRule); err == nil { + t.Error("invalid regexp rule loaded: replaceUserRule()") + } + }) +} + +func TestLiveReload(t *testing.T) { + t.Parallel() + t.Log("Test rules loader with live reload") + l, err := NewLoader(true) + if err != nil { + t.Fail() + } + if err = Copy("testdata/000-allow-chrome.json", tmpDir+"/000-allow-chrome.json"); err != nil { + t.Error("Error copying rule into a temp dir") + } + if err = Copy("testdata/001-deny-chrome.json", tmpDir+"/001-deny-chrome.json"); err != nil { + t.Error("Error copying rule into a temp dir") + } + if err = l.Load(tmpDir); err != nil { + t.Error("Error loading test rules: ", err) + } + //wait for watcher to activate + time.Sleep(time.Second) + if err = Copy("testdata/live_reload/test-live-reload-remove.json", tmpDir+"/test-live-reload-remove.json"); err != nil { + t.Error("Error copying rules into temp dir") + } + if err = Copy("testdata/live_reload/test-live-reload-delete.json", tmpDir+"/test-live-reload-delete.json"); err != nil { + t.Error("Error copying rules into temp dir") + } + //wait for watcher to pick up the changes + time.Sleep(time.Second) + testNumRules(t, l, 4) + if err = os.Remove(tmpDir + "/test-live-reload-remove.json"); err != nil { + t.Error("Error Remove()ing file from temp dir") + } + if err = l.Delete("test-live-reload-delete"); err != nil { + t.Error("Error Delete()ing file from temp dir") + } + //wait for watcher to pick up the changes + time.Sleep(time.Second) + testNumRules(t, l, 2) +} + +func randString() string { + rand.Seed(time.Now().UnixNano()) + var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + b := make([]rune, 10) + for i := range b { + b[i] = letterRunes[rand.Intn(len(letterRunes))] + } + return string(b) +} + +func Copy(src, dst string) error { + in, err := os.Open(src) + if err != nil { + return err + } + defer in.Close() + + out, err := os.Create(dst) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, in) + if err != nil { + return err + } + return out.Close() +} + +func testNumRules(t *testing.T, l *Loader, num int) { + if l.NumRules() != num { + t.Error("rules number should be (2): ", num) + } +} + +func testRulesOrder(t *testing.T, l *Loader) { + if l.rulesKeys[0] != "000-aaa-name" { + t.Error("Rules not in order (0): ", l.rulesKeys) + } + if l.rulesKeys[1] != "000-allow-chrome" { + t.Error("Rules not in order (1): ", l.rulesKeys) + } + if l.rulesKeys[2] != "001-deny-chrome" { + t.Error("Rules not in order (2): ", l.rulesKeys) + } +} + +func testSortRules(t *testing.T, l *Loader) { + l.rulesKeys[1] = "001-deny-chrome" + l.rulesKeys[2] = "000-allow-chrome" + l.sortRules() + if l.rulesKeys[1] != "000-allow-chrome" { + t.Error("Rules not in order (1): ", l.rulesKeys) + } + if l.rulesKeys[2] != "001-deny-chrome" { + t.Error("Rules not in order (2): ", l.rulesKeys) + } +} + +func testFindMatch(t *testing.T, l *Loader) { + conn.Process.Path = "/opt/google/chrome/chrome" + + testFindPriorityMatch(t, l) + testFindDenyMatch(t, l) + testFindAllowMatch(t, l) + + restoreConnection() +} + +func testFindPriorityMatch(t *testing.T, l *Loader) { + match := l.FindFirstMatch(conn) + if match == nil { + t.Error("FindPriorityMatch didn't match") + } + // test 000-allow-chrome, priority == true + if match.Name != "000-allow-chrome" { + t.Error("findPriorityMatch: priority rule failed: ", match) + } + +} + +func testFindDenyMatch(t *testing.T, l *Loader) { + l.rules["000-allow-chrome"].Precedence = false + // test 000-allow-chrome, priority == false + // 001-deny-chrome must match + match := l.FindFirstMatch(conn) + if match == nil { + t.Error("FindDenyMatch deny didn't match") + } + if match.Name != "001-deny-chrome" { + t.Error("findDenyMatch: deny rule failed: ", match) + } +} + +func testFindAllowMatch(t *testing.T, l *Loader) { + l.rules["000-allow-chrome"].Precedence = false + l.rules["001-deny-chrome"].Action = Allow + // test 000-allow-chrome, priority == false + // 001-deny-chrome must match + match := l.FindFirstMatch(conn) + if match == nil { + t.Error("FindAllowMatch allow didn't match") + } + if match.Name != "001-deny-chrome" { + t.Error("findAllowMatch: allow rule failed: ", match) + } +} + +func testFindEnabled(t *testing.T, l *Loader) { + l.rules["000-allow-chrome"].Precedence = false + l.rules["001-deny-chrome"].Action = Allow + l.rules["001-deny-chrome"].Enabled = false + // test 000-allow-chrome, priority == false + // 001-deny-chrome must match + match := l.FindFirstMatch(conn) + if match == nil { + t.Error("FindEnabledMatch, match nil") + } + if match.Name == "001-deny-chrome" { + t.Error("findEnabledMatch: deny rule shouldn't have matched: ", match) + } +} + +// test that changing the Duration of a temporary rule doesn't delete +// the new one, ignoring the old timer. +func testDurationChange(t *testing.T, l *Loader) { + l.rules["000-aaa-name"].Duration = "2s" + if err := l.replaceUserRule(l.rules["000-aaa-name"]); err != nil { + t.Error("testDurationChange, error replacing rule: ", err) + } + l.rules["000-aaa-name"].Duration = "1h" + if err := l.replaceUserRule(l.rules["000-aaa-name"]); err != nil { + t.Error("testDurationChange, error replacing rule: ", err) + } + time.Sleep(time.Second * 4) + if _, found := l.rules["000-aaa-name"]; !found { + t.Error("testDurationChange, error: rule has been deleted") + } +} diff --git a/daemon/rule/operator.go b/daemon/rule/operator.go new file mode 100644 index 0000000..45cbc34 --- /dev/null +++ b/daemon/rule/operator.go @@ -0,0 +1,297 @@ +package rule + +import ( + "fmt" + "net" + "reflect" + "regexp" + "strings" + "sync" + + "github.com/evilsocket/opensnitch/daemon/conman" + "github.com/evilsocket/opensnitch/daemon/core" + "github.com/evilsocket/opensnitch/daemon/log" +) + +// Type is the type of rule. +// Every type has its own way of checking the user data against connections. +type Type string + +// Sensitive defines if a rule is case-sensitive or not. By default no. +type Sensitive bool + +// Operand is what we check on a connection. +type Operand string + +// Available types +const ( + Simple = Type("simple") + Regexp = Type("regexp") + Complex = Type("complex") // for future use + List = Type("list") + Network = Type("network") + Lists = Type("lists") +) + +// Available operands +const ( + OpTrue = Operand("true") + OpProcessID = Operand("process.id") + OpProcessPath = Operand("process.path") + OpProcessCmd = Operand("process.command") + OpProcessEnvPrefix = Operand("process.env.") + OpProcessEnvPrefixLen = 12 + OpUserID = Operand("user.id") + OpDstIP = Operand("dest.ip") + OpDstHost = Operand("dest.host") + OpDstPort = Operand("dest.port") + OpDstNetwork = Operand("dest.network") + OpProto = Operand("protocol") + OpList = Operand("list") + OpDomainsLists = Operand("lists.domains") + OpDomainsRegexpLists = Operand("lists.domains_regexp") + OpIPLists = Operand("lists.ips") + OpNetLists = Operand("lists.nets") +) + +type opCallback func(value interface{}) bool + +// Operator represents what we want to filter of a connection, and how. +type Operator struct { + Type Type `json:"type"` + Operand Operand `json:"operand"` + Sensitive Sensitive `json:"sensitive"` + Data string `json:"data"` + List []Operator `json:"list"` + + sync.RWMutex + cb opCallback + re *regexp.Regexp + netMask *net.IPNet + isCompiled bool + lists map[string]interface{} + listsMonitorRunning bool + exitMonitorChan chan (bool) +} + +// NewOperator returns a new operator object +func NewOperator(t Type, s Sensitive, o Operand, data string, list []Operator) (*Operator, error) { + op := Operator{ + Type: t, + Sensitive: s, + Operand: o, + Data: data, + List: list, + } + return &op, nil +} + +// Compile translates the operator type field to its callback counterpart +func (o *Operator) Compile() error { + if o.isCompiled { + return nil + } + if o.Type == Simple { + o.cb = o.simpleCmp + } else if o.Type == Regexp { + o.cb = o.reCmp + if o.Sensitive == false { + o.Data = strings.ToLower(o.Data) + } + re, err := regexp.Compile(o.Data) + if err != nil { + return err + } + o.re = re + } else if o.Operand == OpDomainsLists { + if o.Data == "" { + return fmt.Errorf("Operand lists is empty, nothing to load: %s", o) + } + o.loadLists() + o.cb = o.domainsListCmp + } else if o.Operand == OpDomainsRegexpLists { + if o.Data == "" { + return fmt.Errorf("Operand regexp lists is empty, nothing to load: %s", o) + } + o.loadLists() + o.cb = o.reListCmp + } else if o.Operand == OpIPLists { + if o.Data == "" { + return fmt.Errorf("Operand ip lists is empty, nothing to load: %s", o) + } + o.loadLists() + o.cb = o.ipListCmp + } else if o.Operand == OpNetLists { + if o.Data == "" { + return fmt.Errorf("Operand net lists is empty, nothing to load: %s", o) + } + o.loadLists() + o.cb = o.ipNetCmp + } else if o.Type == List { + o.Operand = OpList + } else if o.Type == Network { + var err error + _, o.netMask, err = net.ParseCIDR(o.Data) + if err != nil { + return err + } + o.cb = o.cmpNetwork + } + log.Debug("Operator compiled: %s", o) + o.isCompiled = true + + return nil +} + +func (o *Operator) String() string { + how := "is" + if o.Type == Regexp { + how = "matches" + } + return fmt.Sprintf("%s %s '%s'", log.Bold(string(o.Operand)), how, log.Yellow(string(o.Data))) +} + +func (o *Operator) simpleCmp(v interface{}) bool { + if o.Sensitive == false { + return strings.EqualFold(v.(string), o.Data) + } + return v == o.Data +} + +func (o *Operator) reCmp(v interface{}) bool { + if vt := reflect.ValueOf(v).Kind(); vt != reflect.String { + log.Warning("Operator.reCmp() bad interface type: %T", v) + return false + } + if o.Sensitive == false { + v = strings.ToLower(v.(string)) + } + return o.re.MatchString(v.(string)) +} + +func (o *Operator) cmpNetwork(destIP interface{}) bool { + // 192.0.2.1/24, 2001:db8:a0b:12f0::1/32 + if o.netMask == nil { + log.Warning("cmpNetwork() NULL: %s", destIP) + return false + } + return o.netMask.Contains(destIP.(net.IP)) +} + +func (o *Operator) domainsListCmp(v interface{}) bool { + dstHost := v.(string) + if dstHost == "" { + return false + } + if o.Sensitive == false { + dstHost = strings.ToLower(dstHost) + } + o.RLock() + defer o.RUnlock() + + if _, found := o.lists[dstHost]; found { + log.Debug("%s: %s, %s", log.Red("domain list match"), dstHost, o.lists[dstHost]) + return true + } + return false +} + +func (o *Operator) ipListCmp(v interface{}) bool { + dstIP := v.(string) + if dstIP == "" { + return false + } + o.RLock() + defer o.RUnlock() + + if _, found := o.lists[dstIP]; found { + log.Debug("%s: %s, %s", log.Red("IP list match"), dstIP, o.lists[dstIP].(string)) + return true + } + return false +} + +func (o *Operator) ipNetCmp(dstIP interface{}) bool { + o.RLock() + defer o.RUnlock() + + for host, netMask := range o.lists { + n := netMask.(*net.IPNet) + if n.Contains(dstIP.(net.IP)) { + log.Debug("%s: %s, %s", log.Red("Net list match"), dstIP, host) + return true + } + } + return false +} + +func (o *Operator) reListCmp(v interface{}) bool { + dstHost := v.(string) + if dstHost == "" { + return false + } + if o.Sensitive == false { + dstHost = strings.ToLower(dstHost) + } + o.RLock() + defer o.RUnlock() + + for file, re := range o.lists { + r := re.(*regexp.Regexp) + if r.MatchString(dstHost) { + log.Debug("%s: %s, %s", log.Red("Regexp list match"), dstHost, file) + return true + } + } + return false +} + +func (o *Operator) listMatch(con interface{}) bool { + res := true + for i := 0; i < len(o.List); i++ { + res = res && o.List[i].Match(con.(*conman.Connection)) + } + return res +} + +// Match tries to match parts of a connection with the given operator. +func (o *Operator) Match(con *conman.Connection) bool { + + if o.Operand == OpTrue { + return true + } else if o.Operand == OpList { + return o.listMatch(con) + } else if o.Operand == OpProcessPath { + return o.cb(con.Process.Path) + } else if o.Operand == OpProcessCmd { + return o.cb(strings.Join(con.Process.Args, " ")) + } else if o.Operand == OpDstHost && con.DstHost != "" { + return o.cb(con.DstHost) + } else if o.Operand == OpDstIP { + return o.cb(con.DstIP.String()) + } else if o.Operand == OpDstPort { + return o.cb(fmt.Sprintf("%d", con.DstPort)) + } else if o.Operand == OpUserID { + return o.cb(fmt.Sprintf("%d", con.Entry.UserId)) + } else if o.Operand == OpProcessID { + return o.cb(fmt.Sprint(con.Process.ID)) + } else if o.Operand == OpDomainsLists { + return o.cb(con.DstHost) + } else if o.Operand == OpIPLists { + return o.cb(con.DstIP.String()) + } else if o.Operand == OpDstNetwork { + return o.cb(con.DstIP) + } else if o.Operand == OpNetLists { + return o.cb(con.DstIP) + } else if o.Operand == OpDomainsRegexpLists { + return o.cb(con.DstHost) + } else if o.Operand == OpProto { + return o.cb(con.Protocol) + } else if strings.HasPrefix(string(o.Operand), string(OpProcessEnvPrefix)) { + envVarName := core.Trim(string(o.Operand[OpProcessEnvPrefixLen:])) + envVarValue, _ := con.Process.Env[envVarName] + return o.cb(envVarValue) + } + + return false +} diff --git a/daemon/rule/operator_lists.go b/daemon/rule/operator_lists.go new file mode 100644 index 0000000..dd05635 --- /dev/null +++ b/daemon/rule/operator_lists.go @@ -0,0 +1,263 @@ +package rule + +import ( + "fmt" + "io/ioutil" + "net" + "path/filepath" + "regexp" + "runtime/debug" + "strings" + "time" + + "github.com/evilsocket/opensnitch/daemon/core" + "github.com/evilsocket/opensnitch/daemon/log" +) + +func (o *Operator) monitorLists() { + log.Info("monitor lists started: %s", o.Data) + + modTimes := make(map[string]time.Time) + totalFiles := 0 + needReload := false + numFiles := 0 + + expr := filepath.Join(o.Data, "/*.*") + for { + select { + case <-o.exitMonitorChan: + goto Exit + default: + fileList, err := filepath.Glob(expr) + if err != nil { + log.Warning("Error reading directory of domains list: %s, %s", o.Data, err) + goto Exit + } + numFiles = 0 + + for _, filename := range fileList { + // ignore hidden files + name := filepath.Base(filename) + if name[:1] == "." { + delete(modTimes, filename) + continue + } + // an overwrite operation performs two tasks: truncate the file and save the new content, + // causing the file time to be modified twice. + modTime, err := core.GetFileModTime(filename) + if err != nil { + log.Debug("deleting saved mod time due to error reading the list, %s", filename) + delete(modTimes, filename) + } else if lastModTime, found := modTimes[filename]; found { + if lastModTime.Equal(modTime) == false { + log.Debug("list changed: %s, %s, %s", lastModTime, modTime, filename) + needReload = true + } + } + modTimes[filename] = modTime + numFiles++ + } + fileList = nil + + if numFiles != totalFiles { + needReload = true + } + totalFiles = numFiles + + if needReload { + // we can't reload a single list, because the domains of all lists are added to the same map. + // we could have the domains separated by lists/files, but then we'd need to iterate the map in order + // to match a domain. Reloading the lists shoud only occur once a day. + if err := o.readLists(); err != nil { + log.Warning("%s", err) + } + needReload = false + } + time.Sleep(4 * time.Second) + } + } + +Exit: + modTimes = nil + o.ClearLists() + log.Info("lists monitor stopped") +} + +// ClearLists deletes all the entries of a list +func (o *Operator) ClearLists() { + o.Lock() + defer o.Unlock() + + log.Info("clearing domains lists: %d - %s", len(o.lists), o.Data) + for k := range o.lists { + delete(o.lists, k) + } + debug.FreeOSMemory() +} + +// StopMonitoringLists stops the monitoring lists goroutine. +func (o *Operator) StopMonitoringLists() { + if o.listsMonitorRunning == true { + o.exitMonitorChan <- true + o.exitMonitorChan = nil + o.listsMonitorRunning = false + } +} + +func (o *Operator) readDomainsList(raw, fileName string) (dups uint64) { + log.Debug("Loading domains list: %s, size: %d", fileName, len(raw)) + lines := strings.Split(string(raw), "\n") + for _, domain := range lines { + if len(domain) < 9 { + continue + } + // exclude not valid lines + if domain[:7] != "0.0.0.0" && domain[:9] != "127.0.0.1" { + continue + } + host := domain[8:] + // exclude localhost entries + if domain[:9] == "127.0.0.1" { + host = domain[10:] + } + if host == "local" || host == "localhost" || host == "localhost.localdomain" || host == "broadcasthost" { + continue + } + + host = core.Trim(host) + if _, found := o.lists[host]; found { + dups++ + continue + } + o.lists[host] = fileName + } + lines = nil + log.Info("%d domains loaded, %s", len(o.lists), fileName) + + return dups +} + +func (o *Operator) readNetList(raw, fileName string) (dups uint64) { + log.Debug("Loading nets list: %s, size: %d", fileName, len(raw)) + lines := strings.Split(string(raw), "\n") + for _, line := range lines { + if line == "" || line[0] == '#' { + continue + } + host := core.Trim(line) + if _, found := o.lists[host]; found { + dups++ + continue + } + _, netMask, err := net.ParseCIDR(host) + if err != nil { + log.Warning("Error parsing net from list: %s, (%s)", err, fileName) + continue + } + o.lists[host] = netMask + } + lines = nil + log.Info("%d nets loaded, %s", len(o.lists), fileName) + + return dups +} + +func (o *Operator) readRegexpList(raw, fileName string) (dups uint64) { + log.Debug("Loading regexp list: %s, size: %d", fileName, len(raw)) + lines := strings.Split(string(raw), "\n") + for n, line := range lines { + if line == "" || line[0] == '#' { + continue + } + host := core.Trim(line) + if _, found := o.lists[host]; found { + dups++ + continue + } + re, err := regexp.Compile(line) + if err != nil { + log.Warning("Error compiling regexp from list: %s, (%d:%s)", err, n, fileName) + continue + } + o.lists[line] = re + } + lines = nil + log.Info("%d regexps loaded, %s", len(o.lists), fileName) + + return dups +} + +func (o *Operator) readIPList(raw, fileName string) (dups uint64) { + log.Debug("Loading IPs list: %s, size: %d", fileName, len(raw)) + lines := strings.Split(string(raw), "\n") + for _, line := range lines { + if line == "" || line[0] == '#' { + continue + } + ip := core.Trim(line) + if _, found := o.lists[ip]; found { + dups++ + continue + } + o.lists[ip] = fileName + } + lines = nil + log.Info("%d IPs loaded, %s", len(o.lists), fileName) + + return dups +} + +func (o *Operator) readLists() error { + o.ClearLists() + + var dups uint64 + // this list is particular to this operator and rule + o.Lock() + defer o.Unlock() + o.lists = make(map[string]interface{}) + + expr := filepath.Join(o.Data, "*.*") + fileList, err := filepath.Glob(expr) + if err != nil { + return fmt.Errorf("Error loading domains lists '%s': %s", expr, err) + } + + for _, fileName := range fileList { + // ignore hidden files + name := filepath.Base(fileName) + if name[:1] == "." { + continue + } + + raw, err := ioutil.ReadFile(fileName) + if err != nil { + log.Warning("Error reading list of IPs (%s): %s", fileName, err) + continue + } + + if o.Operand == OpDomainsLists { + dups += o.readDomainsList(string(raw), fileName) + } else if o.Operand == OpDomainsRegexpLists { + dups += o.readRegexpList(string(raw), fileName) + } else if o.Operand == OpNetLists { + dups += o.readNetList(string(raw), fileName) + } else if o.Operand == OpIPLists { + dups += o.readIPList(string(raw), fileName) + } else { + log.Warning("Unknown lists operand type: %s", o.Operand) + } + } + log.Info("%d lists loaded, %d domains, %d duplicated", len(fileList), len(o.lists), dups) + return nil +} + +func (o *Operator) loadLists() { + log.Info("loading domains lists: %s, %s, %s", o.Type, o.Operand, o.Data) + + // when loading from disk, we don't use the Operator's constructor, so we need to create this channel + if o.exitMonitorChan == nil { + o.exitMonitorChan = make(chan bool) + o.listsMonitorRunning = true + go o.monitorLists() + } +} diff --git a/daemon/rule/operator_test.go b/daemon/rule/operator_test.go new file mode 100644 index 0000000..5d7d07b --- /dev/null +++ b/daemon/rule/operator_test.go @@ -0,0 +1,742 @@ +package rule + +import ( + "encoding/json" + "fmt" + "net" + "testing" + "time" + + "github.com/evilsocket/opensnitch/daemon/conman" + "github.com/evilsocket/opensnitch/daemon/core" + "github.com/evilsocket/opensnitch/daemon/netstat" + "github.com/evilsocket/opensnitch/daemon/procmon" +) + +var ( + defaultProcPath = "/usr/bin/opensnitchd" + defaultProcArgs = "-rules-path /etc/opensnitchd/rules/" + defaultDstHost = "opensnitch.io" + defaultDstPort = uint(443) + defaultDstIP = "185.53.178.14" + defaultUserID = 666 + + netEntry = &netstat.Entry{ + UserId: defaultUserID, + } + + proc = &procmon.Process{ + ID: 12345, + Path: defaultProcPath, + Args: []string{"-rules-path", "/etc/opensnitchd/rules/"}, + } + + conn = &conman.Connection{ + Protocol: "TCP", + SrcPort: 66666, + SrcIP: net.ParseIP("192.168.1.111"), + DstIP: net.ParseIP(defaultDstIP), + DstPort: defaultDstPort, + DstHost: defaultDstHost, + Process: proc, + Entry: netEntry, + } +) + +func compileListOperators(list *[]Operator, t *testing.T) { + op := *list + for i := 0; i < len(*list); i++ { + if err := op[i].Compile(); err != nil { + t.Error("NewOperator List, Compile() subitem error:", err) + } + } +} + +func unmarshalListData(data string, t *testing.T) (op *[]Operator) { + if err := json.Unmarshal([]byte(data), &op); err != nil { + t.Error("Error unmarshalling list data:", err, data) + return nil + } + return op +} + +func restoreConnection() { + conn.Process.Path = defaultProcPath + conn.DstHost = defaultDstHost + conn.DstPort = defaultDstPort + conn.Entry.UserId = defaultUserID +} + +func TestNewOperatorSimple(t *testing.T) { + t.Log("Test NewOperator() simple") + var list []Operator + + opSimple, err := NewOperator(Simple, false, OpTrue, "", list) + if err != nil { + t.Error("NewOperator simple.err should be nil: ", err) + t.Fail() + } + if err = opSimple.Compile(); err != nil { + t.Fail() + } + if opSimple.Match(nil) == false { + t.Error("Test NewOperator() simple.case-insensitive doesn't match") + t.Fail() + } + + t.Run("Operator Simple proc.id", func(t *testing.T) { + // proc.id not sensitive + opSimple, err = NewOperator(Simple, false, OpProcessID, "12345", list) + if err != nil { + t.Error("NewOperator simple.case-insensitive.proc.id err should be nil: ", err) + t.Fail() + } + if err = opSimple.Compile(); err != nil { + t.Error("NewOperator simple.case-insensitive.proc.id Compile() err:", err) + t.Fail() + } + if opSimple.Match(conn) == false { + t.Error("Test NewOperator() simple proc.id doesn't match") + t.Fail() + } + }) + + opSimple, err = NewOperator(Simple, false, OpProcessPath, defaultProcPath, list) + t.Run("Operator Simple proc.path case-insensitive", func(t *testing.T) { + // proc path not sensitive + if err != nil { + t.Error("NewOperator simple proc.path err should be nil: ", err) + t.Fail() + } + if err = opSimple.Compile(); err != nil { + t.Error("NewOperator simple.case-insensitive.proc.path Compile() err:", err) + t.Fail() + } + if opSimple.Match(conn) == false { + t.Error("Test NewOperator() simple proc.path doesn't match") + t.Fail() + } + }) + + t.Run("Operator Simple proc.path sensitive", func(t *testing.T) { + // proc path sensitive + opSimple.Sensitive = true + conn.Process.Path = "/usr/bin/OpenSnitchd" + if opSimple.Match(conn) == true { + t.Error("Test NewOperator() simple proc.path sensitive match") + t.Fail() + } + }) + + opSimple, err = NewOperator(Simple, false, OpDstHost, defaultDstHost, list) + t.Run("Operator Simple con.dstHost case-insensitive", func(t *testing.T) { + // proc dst host not sensitive + if err != nil { + t.Error("NewOperator simple proc.path err should be nil: ", err) + t.Fail() + } + if err = opSimple.Compile(); err != nil { + t.Error("NewOperator simple.case-insensitive.dstHost Compile() err:", err) + t.Fail() + } + if opSimple.Match(conn) == false { + t.Error("Test NewOperator() simple.conn.dstHost.not-sensitive doesn't match") + t.Fail() + } + }) + + t.Run("Operator Simple con.dstHost case-insensitive different host", func(t *testing.T) { + conn.DstHost = "www.opensnitch.io" + if opSimple.Match(conn) == true { + t.Error("Test NewOperator() simple.conn.dstHost.not-sensitive doesn't MATCH") + t.Fail() + } + }) + + t.Run("Operator Simple con.dstHost sensitive", func(t *testing.T) { + // proc dst host sensitive + opSimple, err = NewOperator(Simple, true, OpDstHost, "OpEnsNitCh.io", list) + if err != nil { + t.Error("NewOperator simple.dstHost.sensitive err should be nil: ", err) + t.Fail() + } + if err = opSimple.Compile(); err != nil { + t.Error("NewOperator simple.dstHost.sensitive Compile() err:", err) + t.Fail() + } + conn.DstHost = "OpEnsNitCh.io" + if opSimple.Match(conn) == false { + t.Error("Test NewOperator() simple.dstHost.sensitive doesn't match") + t.Fail() + } + }) + + t.Run("Operator Simple proc.args case-insensitive", func(t *testing.T) { + // proc args case-insensitive + opSimple, err = NewOperator(Simple, false, OpProcessCmd, defaultProcArgs, list) + if err != nil { + t.Error("NewOperator simple proc.args err should be nil: ", err) + t.Fail() + } + if err = opSimple.Compile(); err != nil { + t.Error("NewOperator simple proc.args Compile() err: ", err) + t.Fail() + } + if opSimple.Match(conn) == false { + t.Error("Test NewOperator() simple proc.args doesn't match") + t.Fail() + } + }) + + t.Run("Operator Simple con.dstIp case-insensitive", func(t *testing.T) { + // proc dstIp case-insensitive + opSimple, err = NewOperator(Simple, false, OpDstIP, defaultDstIP, list) + if err != nil { + t.Error("NewOperator simple conn.dstip.err should be nil: ", err) + t.Fail() + } + if err = opSimple.Compile(); err != nil { + t.Error("NewOperator simple con.dstIp Compile() err: ", err) + t.Fail() + } + if opSimple.Match(conn) == false { + t.Error("Test NewOperator() simple conn.dstip doesn't match") + t.Fail() + } + }) + + t.Run("Operator Simple UserId case-insensitive", func(t *testing.T) { + // conn.uid case-insensitive + opSimple, err = NewOperator(Simple, false, OpUserID, fmt.Sprint(defaultUserID), list) + if err != nil { + t.Error("NewOperator simple conn.userid.err should be nil: ", err) + t.Fail() + } + if err = opSimple.Compile(); err != nil { + t.Error("NewOperator simple UserId Compile() err: ", err) + t.Fail() + } + if opSimple.Match(conn) == false { + t.Error("Test NewOperator() simple conn.userid doesn't match") + t.Fail() + } + }) + + restoreConnection() +} + +func TestNewOperatorNetwork(t *testing.T) { + t.Log("Test NewOperator() network") + var dummyList []Operator + + opSimple, err := NewOperator(Network, false, OpDstNetwork, "185.53.178.14/24", dummyList) + if err != nil { + t.Error("NewOperator network.err should be nil: ", err) + t.Fail() + } + if err = opSimple.Compile(); err != nil { + t.Fail() + } + if opSimple.Match(conn) == false { + t.Error("Test NewOperator() network doesn't match") + t.Fail() + } + + opSimple, err = NewOperator(Network, false, OpDstNetwork, "8.8.8.8/24", dummyList) + if err != nil { + t.Error("NewOperator network.err should be nil: ", err) + t.Fail() + } + if err = opSimple.Compile(); err != nil { + t.Fail() + } + if opSimple.Match(conn) == true { + t.Error("Test NewOperator() network doesn't match:", conn.DstIP) + t.Fail() + } + + restoreConnection() +} + +func TestNewOperatorRegexp(t *testing.T) { + t.Log("Test NewOperator() regexp") + var dummyList []Operator + + opRE, err := NewOperator(Regexp, false, OpProto, "^TCP$", dummyList) + if err != nil { + t.Error("NewOperator regexp.err should be nil: ", err) + t.Fail() + } + if err = opRE.Compile(); err != nil { + t.Fail() + } + if opRE.Match(conn) == false { + t.Error("Test NewOperator() regexp doesn't match") + t.Fail() + } + + restoreConnection() +} + +func TestNewOperatorInvalidRegexp(t *testing.T) { + t.Log("Test NewOperator() invalid regexp") + var dummyList []Operator + + opRE, err := NewOperator(Regexp, false, OpProto, "^TC(P$", dummyList) + if err != nil { + t.Error("NewOperator regexp.err should be nil: ", err) + t.Fail() + } + if err = opRE.Compile(); err == nil { + t.Error("NewOperator() invalid regexp. It should fail: ", err) + t.Fail() + } + + restoreConnection() +} + +func TestNewOperatorRegexpSensitive(t *testing.T) { + t.Log("Test NewOperator() regexp sensitive") + var dummyList []Operator + + var sensitive Sensitive + sensitive = true + + conn.Process.Path = "/tmp/cUrL" + + opRE, err := NewOperator(Regexp, sensitive, OpProcessPath, "^/tmp/cUrL$", dummyList) + if err != nil { + t.Error("NewOperator regexp.case-sensitive.err should be nil: ", err) + t.Fail() + } + if err = opRE.Compile(); err != nil { + t.Fail() + } + if opRE.Match(conn) == false { + t.Error("Test NewOperator() RE sensitive doesn't match:", conn.Process.Path) + t.Fail() + } + + t.Run("Operator regexp proc.path case-sensitive", func(t *testing.T) { + conn.Process.Path = "/tmp/curl" + if opRE.Match(conn) == true { + t.Error("Test NewOperator() RE sensitive match:", conn.Process.Path) + t.Fail() + } + }) + + opRE, err = NewOperator(Regexp, !sensitive, OpProcessPath, "^/tmp/cUrL$", dummyList) + if err != nil { + t.Error("NewOperator regexp.case-insensitive.err should be nil: ", err) + t.Fail() + } + if err = opRE.Compile(); err != nil { + t.Fail() + } + if opRE.Match(conn) == false { + t.Error("Test NewOperator() RE not sensitive match:", conn.Process.Path) + t.Fail() + } + + restoreConnection() +} + +func TestNewOperatorList(t *testing.T) { + t.Log("Test NewOperator() List") + var list []Operator + listData := `[{"type": "simple", "operand": "dest.ip", "data": "185.53.178.14", "sensitive": false}, {"type": "simple", "operand": "dest.port", "data": "443", "sensitive": false}]` + + // simple list + opList, err := NewOperator(List, false, OpProto, listData, list) + t.Run("Operator List simple case-insensitive", func(t *testing.T) { + if err != nil { + t.Error("NewOperator list.regexp.err should be nil: ", err) + t.Fail() + } + if err = opList.Compile(); err != nil { + t.Fail() + } + opList.List = *unmarshalListData(opList.Data, t) + compileListOperators(&opList.List, t) + if opList.Match(conn) == false { + t.Error("Test NewOperator() list simple doesn't match") + t.Fail() + } + }) + + t.Run("Operator List regexp case-insensitive", func(t *testing.T) { + // list with regexp, case-insensitive + listData = `[{"type": "regexp", "operand": "process.path", "data": "^/usr/bin/.*", "sensitive": false},{"type": "simple", "operand": "dest.ip", "data": "185.53.178.14", "sensitive": false}, {"type": "simple", "operand": "dest.port", "data": "443", "sensitive": false}]` + opList.List = *unmarshalListData(listData, t) + compileListOperators(&opList.List, t) + if err = opList.Compile(); err != nil { + t.Fail() + } + if opList.Match(conn) == false { + t.Error("Test NewOperator() list regexp doesn't match") + t.Fail() + } + }) + + t.Run("Operator List regexp case-sensitive", func(t *testing.T) { + // list with regexp, case-sensitive + // "data": "^/usr/BiN/.*" must match conn.Process.Path (sensitive) + listData = `[{"type": "regexp", "operand": "process.path", "data": "^/usr/BiN/.*", "sensitive": false},{"type": "simple", "operand": "dest.ip", "data": "185.53.178.14", "sensitive": false}, {"type": "simple", "operand": "dest.port", "data": "443", "sensitive": false}]` + opList.List = *unmarshalListData(listData, t) + compileListOperators(&opList.List, t) + conn.Process.Path = "/usr/BiN/opensnitchd" + opList.Sensitive = true + if err = opList.Compile(); err != nil { + t.Fail() + } + if opList.Match(conn) == false { + t.Error("Test NewOperator() list.regexp.sensitive doesn't match:", conn.Process.Path) + t.Fail() + } + }) + + t.Run("Operator List regexp case-insensitive 2", func(t *testing.T) { + // "data": "^/usr/BiN/.*" must not match conn.Process.Path (insensitive) + opList.Sensitive = false + conn.Process.Path = "/USR/BiN/opensnitchd" + if err = opList.Compile(); err != nil { + t.Fail() + } + if opList.Match(conn) == false { + t.Error("Test NewOperator() list.regexp.insensitive match:", conn.Process.Path) + t.Fail() + } + }) + + t.Run("Operator List regexp case-insensitive 3", func(t *testing.T) { + // "data": "^/usr/BiN/.*" must match conn.Process.Path (insensitive) + opList.Sensitive = false + conn.Process.Path = "/USR/bin/opensnitchd" + if err = opList.Compile(); err != nil { + t.Fail() + } + if opList.Match(conn) == false { + t.Error("Test NewOperator() list.regexp.insensitive match:", conn.Process.Path) + t.Fail() + } + }) + + restoreConnection() +} + +func TestNewOperatorListsSimple(t *testing.T) { + t.Log("Test NewOperator() Lists simple") + var dummyList []Operator + + opLists, err := NewOperator(Lists, false, OpDomainsLists, "testdata/lists/domains/", dummyList) + if err != nil { + t.Error("NewOperator Lists, shouldn't be nil: ", err) + t.Fail() + } + if err = opLists.Compile(); err != nil { + t.Error("NewOperator Lists, Compile() error:", err) + } + time.Sleep(time.Second) + t.Log("testing Lists, DstHost:", conn.DstHost) + // The list contains 4 lines, 1 is a comment and there's a domain duplicated. + // We should only load lines that start with 0.0.0.0 or 127.0.0.1 + if len(opLists.lists) != 2 { + t.Error("NewOperator Lists, number of domains error:", opLists.lists, len(opLists.lists)) + } + if opLists.Match(conn) == false { + t.Error("Test NewOperator() lists doesn't match") + } + + opLists.StopMonitoringLists() + time.Sleep(time.Second) + opLists.Lock() + if len(opLists.lists) != 0 { + t.Error("NewOperator Lists, number should be 0 after stop:", opLists.lists, len(opLists.lists)) + } + opLists.Unlock() + + restoreConnection() +} + +func TestNewOperatorListsIPs(t *testing.T) { + t.Log("Test NewOperator() Lists domains_regexp") + + var subOp *Operator + var list []Operator + listData := `[{"type": "simple", "operand": "user.id", "data": "666", "sensitive": false}, {"type": "lists", "operand": "lists.ips", "data": "testdata/lists/ips/", "sensitive": false}]` + + opLists, err := NewOperator(List, false, OpList, listData, list) + if err != nil { + t.Error("NewOperator Lists domains_regexp, shouldn't be nil: ", err) + t.Fail() + } + if err := opLists.Compile(); err != nil { + t.Error("NewOperator Lists domains_regexp, Compile() error:", err) + } + opLists.List = *unmarshalListData(opLists.Data, t) + for i := 0; i < len(opLists.List); i++ { + if err := opLists.List[i].Compile(); err != nil { + t.Error("NewOperator Lists domains_regexp, Compile() subitem error:", err) + } + if opLists.List[i].Type == Lists { + subOp = &opLists.List[i] + } + } + + time.Sleep(time.Second) + if opLists.Match(conn) == false { + t.Error("Test NewOperator() Lists domains_regexp, doesn't match:", conn.DstHost) + } + + subOp.Lock() + listslen := len(subOp.lists) + subOp.Unlock() + if listslen != 2 { + t.Error("NewOperator Lists domains_regexp, number of domains error:", subOp.lists) + } + + //t.Log("checking lists.domains_regexp:", tries, conn.DstHost) + if opLists.Match(conn) == false { + // we don't care about if it matches, we're testing race conditions + t.Log("Test NewOperator() Lists domains_regexp, doesn't match:", conn.DstHost) + } + + subOp.StopMonitoringLists() + time.Sleep(time.Second) + subOp.Lock() + if len(subOp.lists) != 0 { + t.Error("NewOperator Lists number should be 0:", subOp.lists, len(subOp.lists)) + } + subOp.Unlock() + + restoreConnection() +} + +func TestNewOperatorListsNETs(t *testing.T) { + t.Log("Test NewOperator() Lists domains_regexp") + + var subOp *Operator + var list []Operator + listData := `[{"type": "simple", "operand": "user.id", "data": "666", "sensitive": false}, {"type": "lists", "operand": "lists.nets", "data": "testdata/lists/nets/", "sensitive": false}]` + + opLists, err := NewOperator(List, false, OpList, listData, list) + if err != nil { + t.Error("NewOperator Lists domains_regexp, shouldn't be nil: ", err) + t.Fail() + } + if err := opLists.Compile(); err != nil { + t.Error("NewOperator Lists domains_regexp, Compile() error:", err) + } + opLists.List = *unmarshalListData(opLists.Data, t) + for i := 0; i < len(opLists.List); i++ { + if err := opLists.List[i].Compile(); err != nil { + t.Error("NewOperator Lists domains_regexp, Compile() subitem error:", err) + } + if opLists.List[i].Type == Lists { + subOp = &opLists.List[i] + } + } + + time.Sleep(time.Second) + if opLists.Match(conn) == false { + t.Error("Test NewOperator() Lists domains_regexp, doesn't match:", conn.DstHost) + } + + subOp.Lock() + listslen := len(subOp.lists) + subOp.Unlock() + if listslen != 2 { + t.Error("NewOperator Lists domains_regexp, number of domains error:", subOp.lists) + } + + //t.Log("checking lists.domains_regexp:", tries, conn.DstHost) + if opLists.Match(conn) == false { + // we don't care about if it matches, we're testing race conditions + t.Log("Test NewOperator() Lists domains_regexp, doesn't match:", conn.DstHost) + } + + subOp.StopMonitoringLists() + time.Sleep(time.Second) + subOp.Lock() + if len(subOp.lists) != 0 { + t.Error("NewOperator Lists number should be 0:", subOp.lists, len(subOp.lists)) + } + subOp.Unlock() + + restoreConnection() +} + +func TestNewOperatorListsComplex(t *testing.T) { + t.Log("Test NewOperator() Lists complex") + var subOp *Operator + var list []Operator + listData := `[{"type": "simple", "operand": "user.id", "data": "666", "sensitive": false}, {"type": "lists", "operand": "lists.domains", "data": "testdata/lists/domains/", "sensitive": false}]` + + opLists, err := NewOperator(List, false, OpList, listData, list) + if err != nil { + t.Error("NewOperator Lists complex, shouldn't be nil: ", err) + t.Fail() + } + if err := opLists.Compile(); err != nil { + t.Error("NewOperator Lists complex, Compile() error:", err) + } + opLists.List = *unmarshalListData(opLists.Data, t) + for i := 0; i < len(opLists.List); i++ { + if err := opLists.List[i].Compile(); err != nil { + t.Error("NewOperator Lists complex, Compile() subitem error:", err) + } + if opLists.List[i].Type == Lists { + subOp = &opLists.List[i] + } + } + time.Sleep(time.Second) + subOp.Lock() + if len(subOp.lists) != 2 { + t.Error("NewOperator Lists complex, number of domains error:", subOp.lists) + } + subOp.Unlock() + if opLists.Match(conn) == false { + t.Error("Test NewOperator() Lists complex, doesn't match") + } + + subOp.StopMonitoringLists() + time.Sleep(time.Second) + subOp.Lock() + if len(subOp.lists) != 0 { + t.Error("NewOperator Lists number should be 0:", subOp.lists, len(subOp.lists)) + } + subOp.Unlock() + + restoreConnection() +} + +func TestNewOperatorListsDomainsRegexp(t *testing.T) { + t.Log("Test NewOperator() Lists domains_regexp") + + var subOp *Operator + var list []Operator + listData := `[{"type": "simple", "operand": "user.id", "data": "666", "sensitive": false}, {"type": "lists", "operand": "lists.domains_regexp", "data": "testdata/lists/regexp/", "sensitive": false}]` + + opLists, err := NewOperator(List, false, OpList, listData, list) + if err != nil { + t.Error("NewOperator Lists domains_regexp, shouldn't be nil: ", err) + t.Fail() + } + if err := opLists.Compile(); err != nil { + t.Error("NewOperator Lists domains_regexp, Compile() error:", err) + } + opLists.List = *unmarshalListData(opLists.Data, t) + for i := 0; i < len(opLists.List); i++ { + if err := opLists.List[i].Compile(); err != nil { + t.Error("NewOperator Lists domains_regexp, Compile() subitem error:", err) + } + if opLists.List[i].Type == Lists { + subOp = &opLists.List[i] + } + } + + time.Sleep(time.Second) + if opLists.Match(conn) == false { + t.Error("Test NewOperator() Lists domains_regexp, doesn't match:", conn.DstHost) + } + + subOp.Lock() + listslen := len(subOp.lists) + subOp.Unlock() + if listslen != 2 { + t.Error("NewOperator Lists domains_regexp, number of domains error:", subOp.lists) + } + + //t.Log("checking lists.domains_regexp:", tries, conn.DstHost) + if opLists.Match(conn) == false { + // we don't care about if it matches, we're testing race conditions + t.Log("Test NewOperator() Lists domains_regexp, doesn't match:", conn.DstHost) + } + + subOp.StopMonitoringLists() + time.Sleep(time.Second) + subOp.Lock() + if len(subOp.lists) != 0 { + t.Error("NewOperator Lists number should be 0:", subOp.lists, len(subOp.lists)) + } + subOp.Unlock() + + restoreConnection() +} + +// Must be launched with -race to test that we don't cause leaks +// Race occured on operator.go:241 reListCmp().MathString() +// fixed here: 53419fe +func TestRaceNewOperatorListsDomainsRegexp(t *testing.T) { + t.Log("Test NewOperator() Lists domains_regexp") + + var subOp *Operator + var list []Operator + listData := `[{"type": "simple", "operand": "user.id", "data": "666", "sensitive": false}, {"type": "lists", "operand": "lists.domains_regexp", "data": "testdata/lists/regexp/", "sensitive": false}]` + + opLists, err := NewOperator(List, false, OpList, listData, list) + if err != nil { + t.Error("NewOperator Lists domains_regexp, shouldn't be nil: ", err) + t.Fail() + } + if err := opLists.Compile(); err != nil { + t.Error("NewOperator Lists domains_regexp, Compile() error:", err) + } + opLists.List = *unmarshalListData(opLists.Data, t) + for i := 0; i < len(opLists.List); i++ { + if err := opLists.List[i].Compile(); err != nil { + t.Error("NewOperator Lists domains_regexp, Compile() subitem error:", err) + } + if opLists.List[i].Type == Lists { + subOp = &opLists.List[i] + } + } + + // touch domains list in background, to force a reload. + go func() { + touches := 1000 + for { + if touches < 0 { + break + } + core.Exec("/bin/touch", []string{"testdata/lists/regexp/domainsregexp.txt"}) + touches-- + time.Sleep(100 * time.Millisecond) + //t.Log("touching:", touches) + } + }() + + time.Sleep(time.Second) + + subOp.Lock() + listslen := len(subOp.lists) + subOp.Unlock() + if listslen != 2 { + t.Error("NewOperator Lists domains_regexp, number of domains error:", subOp.lists) + } + + tries := 10000 + for { + if tries < 0 { + break + } + //t.Log("checking lists.domains_regexp:", tries, conn.DstHost) + if opLists.Match(conn) == false { + // we don't care about if it matches, we're testing race conditions + t.Log("Test NewOperator() Lists domains_regexp, doesn't match:", conn.DstHost) + } + + tries-- + time.Sleep(10 * time.Millisecond) + } + + subOp.StopMonitoringLists() + time.Sleep(time.Second) + subOp.Lock() + if len(subOp.lists) != 0 { + t.Error("NewOperator Lists number should be 0:", subOp.lists, len(subOp.lists)) + } + subOp.Unlock() + + restoreConnection() +} diff --git a/daemon/rule/rule.go b/daemon/rule/rule.go new file mode 100644 index 0000000..b51cf8f --- /dev/null +++ b/daemon/rule/rule.go @@ -0,0 +1,115 @@ +package rule + +import ( + "fmt" + "time" + + "github.com/evilsocket/opensnitch/daemon/conman" + "github.com/evilsocket/opensnitch/daemon/log" + "github.com/evilsocket/opensnitch/daemon/ui/protocol" +) + +// Action of a rule +type Action string + +// Actions of rules +const ( + Allow = Action("allow") + Deny = Action("deny") + Reject = Action("reject") +) + +// Duration of a rule +type Duration string + +// daemon possible durations +const ( + Once = Duration("once") + Restart = Duration("until restart") + Always = Duration("always") +) + +// Rule represents an action on a connection. +// The fields match the ones saved as json to disk. +// If a .json rule file is modified on disk, it's reloaded automatically. +type Rule struct { + Created time.Time `json:"created"` + Updated time.Time `json:"updated"` + Name string `json:"name"` + Enabled bool `json:"enabled"` + Precedence bool `json:"precedence"` + Action Action `json:"action"` + Duration Duration `json:"duration"` + Operator Operator `json:"operator"` +} + +// Create creates a new rule object with the specified parameters. +func Create(name string, enabled bool, precedence bool, action Action, duration Duration, op *Operator) *Rule { + return &Rule{ + Created: time.Now(), + Enabled: enabled, + Precedence: precedence, + Name: name, + Action: action, + Duration: duration, + Operator: *op, + } +} + +func (r *Rule) String() string { + return fmt.Sprintf("%s: if(%s){ %s %s }", r.Name, r.Operator.String(), r.Action, r.Duration) +} + +// Match performs on a connection the checks a Rule has, to determine if it +// must be allowed or denied. +func (r *Rule) Match(con *conman.Connection) bool { + return r.Operator.Match(con) +} + +// Deserialize translates back the rule received to a Rule object +func Deserialize(reply *protocol.Rule) (*Rule, error) { + if reply.Operator == nil { + log.Warning("Deserialize rule, Operator nil") + return nil, fmt.Errorf("invalid operator") + } + operator, err := NewOperator( + Type(reply.Operator.Type), + Sensitive(reply.Operator.Sensitive), + Operand(reply.Operator.Operand), + reply.Operator.Data, + make([]Operator, 0), + ) + if err != nil { + log.Warning("Deserialize rule, NewOperator() error: %s", err) + return nil, err + } + + return Create( + reply.Name, + reply.Enabled, + reply.Precedence, + Action(reply.Action), + Duration(reply.Duration), + operator, + ), nil +} + +// Serialize translates a Rule to the protocol object +func (r *Rule) Serialize() *protocol.Rule { + if r == nil { + return nil + } + return &protocol.Rule{ + Name: string(r.Name), + Enabled: bool(r.Enabled), + Precedence: bool(r.Precedence), + Action: string(r.Action), + Duration: string(r.Duration), + Operator: &protocol.Operator{ + Type: string(r.Operator.Type), + Sensitive: bool(r.Operator.Sensitive), + Operand: string(r.Operator.Operand), + Data: string(r.Operator.Data), + }, + } +} diff --git a/daemon/rule/rule_test.go b/daemon/rule/rule_test.go new file mode 100644 index 0000000..cc9017a --- /dev/null +++ b/daemon/rule/rule_test.go @@ -0,0 +1,47 @@ +package rule + +import "testing" + +func TestCreate(t *testing.T) { + t.Log("Test: Create rule") + + var list []Operator + oper, _ := NewOperator(Simple, false, OpTrue, "", list) + r := Create("000-test-name", true, false, Allow, Once, oper) + t.Run("New rule must not be nil", func(t *testing.T) { + if r == nil { + t.Error("Create() returned nil") + t.Fail() + } + }) + t.Run("Rule name must be 000-test-name", func(t *testing.T) { + if r.Name != "000-test-name" { + t.Error("Rule name error:", r.Name) + t.Fail() + } + }) + t.Run("Rule must be enabled", func(t *testing.T) { + if r.Enabled == false { + t.Error("Rule Enabled is false:", r) + t.Fail() + } + }) + t.Run("Rule Precedence must be false", func(t *testing.T) { + if r.Precedence == true { + t.Error("Rule Precedence is true:", r) + t.Fail() + } + }) + t.Run("Rule Action must be Allow", func(t *testing.T) { + if r.Action != Allow { + t.Error("Rule Action is not Allow:", r.Action) + t.Fail() + } + }) + t.Run("Rule Duration should be Once", func(t *testing.T) { + if r.Duration != Once { + t.Error("Rule Duration is not Once:", r.Duration) + t.Fail() + } + }) +} diff --git a/daemon/rule/testdata/000-allow-chrome.json b/daemon/rule/testdata/000-allow-chrome.json new file mode 100644 index 0000000..db2c811 --- /dev/null +++ b/daemon/rule/testdata/000-allow-chrome.json @@ -0,0 +1,16 @@ +{ + "created": "2020-12-13T18:06:52.209804547+01:00", + "updated": "2020-12-13T18:06:52.209857713+01:00", + "name": "000-allow-chrome", + "enabled": true, + "precedence": true, + "action": "allow", + "duration": "always", + "operator": { + "type": "simple", + "operand": "process.path", + "sensitive": false, + "data": "/opt/google/chrome/chrome", + "list": [] + } +} \ No newline at end of file diff --git a/daemon/rule/testdata/001-deny-chrome.json b/daemon/rule/testdata/001-deny-chrome.json new file mode 100644 index 0000000..27c266c --- /dev/null +++ b/daemon/rule/testdata/001-deny-chrome.json @@ -0,0 +1,16 @@ +{ + "created": "2020-12-13T17:54:49.067148304+01:00", + "updated": "2020-12-13T17:54:49.067213602+01:00", + "name": "001-deny-chrome", + "enabled": true, + "precedence": false, + "action": "deny", + "duration": "always", + "operator": { + "type": "simple", + "operand": "process.path", + "sensitive": false, + "data": "/opt/google/chrome/chrome", + "list": [] + } +} \ No newline at end of file diff --git a/daemon/rule/testdata/invalid-regexp-list.json b/daemon/rule/testdata/invalid-regexp-list.json new file mode 100644 index 0000000..bd8973f --- /dev/null +++ b/daemon/rule/testdata/invalid-regexp-list.json @@ -0,0 +1,31 @@ +{ + "created": "2020-12-13T18:06:52.209804547+01:00", + "updated": "2020-12-13T18:06:52.209857713+01:00", + "name": "invalid-regexp-list", + "enabled": true, + "precedence": true, + "action": "allow", + "duration": "always", + "operator": { + "type": "list", + "operand": "list", + "sensitive": false, + "data": "[{\"type\": \"regexp\", \"operand\": \"process.path\", \"sensitive\": false, \"data\": \"^(/di(rmngr$\"}, {\"type\": \"simple\", \"operand\": \"dest.port\", \"data\": \"53\", \"sensitive\": false}]", + "list": [ + { + "type": "regexp", + "operand": "process.path", + "sensitive": false, + "data": "^(/di(rmngr)$", + "list": null + }, + { + "type": "simple", + "operand": "dest.port", + "sensitive": false, + "data": "53", + "list": null + } + ] + } +} diff --git a/daemon/rule/testdata/invalid-regexp.json b/daemon/rule/testdata/invalid-regexp.json new file mode 100644 index 0000000..d296098 --- /dev/null +++ b/daemon/rule/testdata/invalid-regexp.json @@ -0,0 +1,16 @@ +{ + "created": "2020-12-13T18:06:52.209804547+01:00", + "updated": "2020-12-13T18:06:52.209857713+01:00", + "name": "invalid-regexp", + "enabled": true, + "precedence": true, + "action": "allow", + "duration": "always", + "operator": { + "type": "regexp", + "operand": "process.path", + "sensitive": false, + "data": "/opt/((.*)google/chrome/chrome", + "list": [] + } +} diff --git a/daemon/rule/testdata/lists/domains/domainlists.txt b/daemon/rule/testdata/lists/domains/domainlists.txt new file mode 100644 index 0000000..6e2f3e2 --- /dev/null +++ b/daemon/rule/testdata/lists/domains/domainlists.txt @@ -0,0 +1,4 @@ +# this line must be ignored, 0.0.0.0 www.test.org +0.0.0.0 www.test.org +127.0.0.1 www.test.org +0.0.0.0 opensnitch.io diff --git a/daemon/rule/testdata/lists/ips/ips.txt b/daemon/rule/testdata/lists/ips/ips.txt new file mode 100644 index 0000000..6514d30 --- /dev/null +++ b/daemon/rule/testdata/lists/ips/ips.txt @@ -0,0 +1,7 @@ +# this line must be ignored, 0.0.0.0 www.test.org + +# empty lines are also ignored +1.1.1.1 +185.53.178.14 +# duplicated entries should be ignored +1.1.1.1 diff --git a/daemon/rule/testdata/lists/nets/nets.txt b/daemon/rule/testdata/lists/nets/nets.txt new file mode 100644 index 0000000..8041c92 --- /dev/null +++ b/daemon/rule/testdata/lists/nets/nets.txt @@ -0,0 +1,8 @@ +# this line must be ignored, 0.0.0.0 www.test.org + +# empty lines are also ignored +1.1.1.0/24 +185.53.178.0/24 +# duplicated entries should be ignored +1.1.1.0/24 + diff --git a/daemon/rule/testdata/lists/regexp/domainsregexp.txt b/daemon/rule/testdata/lists/regexp/domainsregexp.txt new file mode 100644 index 0000000..85ab3e9 --- /dev/null +++ b/daemon/rule/testdata/lists/regexp/domainsregexp.txt @@ -0,0 +1,4 @@ +# this line must be ignored, 0.0.0.0 www.test.org +www.test.org +www.test.org +opensnitch.io diff --git a/daemon/rule/testdata/live_reload/test-live-reload-delete.json b/daemon/rule/testdata/live_reload/test-live-reload-delete.json new file mode 100644 index 0000000..5a4591a --- /dev/null +++ b/daemon/rule/testdata/live_reload/test-live-reload-delete.json @@ -0,0 +1,16 @@ +{ + "created": "2020-12-13T18:06:52.209804547+01:00", + "updated": "2020-12-13T18:06:52.209857713+01:00", + "name": "test-live-reload-delete", + "enabled": true, + "precedence": true, + "action": "deny", + "duration": "always", + "operator": { + "type": "simple", + "operand": "process.path", + "sensitive": false, + "data": "/usr/bin/curl", + "list": [] + } + } \ No newline at end of file diff --git a/daemon/rule/testdata/live_reload/test-live-reload-remove.json b/daemon/rule/testdata/live_reload/test-live-reload-remove.json new file mode 100644 index 0000000..8f21ed9 --- /dev/null +++ b/daemon/rule/testdata/live_reload/test-live-reload-remove.json @@ -0,0 +1,16 @@ +{ + "created": "2020-12-13T18:06:52.209804547+01:00", + "updated": "2020-12-13T18:06:52.209857713+01:00", + "name": "test-live-reload-remove", + "enabled": true, + "precedence": true, + "action": "deny", + "duration": "always", + "operator": { + "type": "simple", + "operand": "process.path", + "sensitive": false, + "data": "/usr/bin/curl", + "list": [] + } + } \ No newline at end of file diff --git a/daemon/statistics/event.go b/daemon/statistics/event.go new file mode 100644 index 0000000..fe9e9ee --- /dev/null +++ b/daemon/statistics/event.go @@ -0,0 +1,32 @@ +package statistics + +import ( + "time" + + "github.com/evilsocket/opensnitch/daemon/conman" + "github.com/evilsocket/opensnitch/daemon/rule" + "github.com/evilsocket/opensnitch/daemon/ui/protocol" +) + +type Event struct { + Time time.Time + Connection *conman.Connection + Rule *rule.Rule +} + +func NewEvent(con *conman.Connection, match *rule.Rule) *Event { + return &Event{ + Time: time.Now(), + Connection: con, + Rule: match, + } +} + +func (e *Event) Serialize() *protocol.Event { + return &protocol.Event{ + Time: e.Time.Format("2006-01-02 15:04:05"), + Connection: e.Connection.Serialize(), + Rule: e.Rule.Serialize(), + Unixnano: e.Time.UnixNano(), + } +} diff --git a/daemon/statistics/stats.go b/daemon/statistics/stats.go new file mode 100644 index 0000000..caa71e1 --- /dev/null +++ b/daemon/statistics/stats.go @@ -0,0 +1,244 @@ +package statistics + +import ( + "fmt" + "sync" + "time" + + "github.com/evilsocket/opensnitch/daemon/conman" + "github.com/evilsocket/opensnitch/daemon/core" + "github.com/evilsocket/opensnitch/daemon/log" + "github.com/evilsocket/opensnitch/daemon/rule" + "github.com/evilsocket/opensnitch/daemon/ui/protocol" +) + +// StatsConfig holds the stats confguration +type StatsConfig struct { + MaxEvents int `json:"MaxEvents"` + MaxStats int `json:"MaxStats"` +} + +type conEvent struct { + con *conman.Connection + match *rule.Rule + wasMissed bool +} + +// Statistics holds the connections and statistics the daemon intercepts. +// The connections are stored in the Events slice. +type Statistics struct { + sync.RWMutex + + Started time.Time + DNSResponses int + Connections int + Ignored int + Accepted int + Dropped int + RuleHits int + RuleMisses int + Events []*Event + ByProto map[string]uint64 + ByAddress map[string]uint64 + ByHost map[string]uint64 + ByPort map[string]uint64 + ByUID map[string]uint64 + ByExecutable map[string]uint64 + + rules *rule.Loader + jobs chan conEvent + // max number of events to keep in the buffer + maxEvents int + // max number of entries for each By* map + maxStats int +} + +// New returns a new Statistics object and initializes the go routines to update the stats. +func New(rules *rule.Loader) (stats *Statistics) { + stats = &Statistics{ + Started: time.Now(), + Events: make([]*Event, 0), + ByProto: make(map[string]uint64), + ByAddress: make(map[string]uint64), + ByHost: make(map[string]uint64), + ByPort: make(map[string]uint64), + ByUID: make(map[string]uint64), + ByExecutable: make(map[string]uint64), + + rules: rules, + jobs: make(chan conEvent), + maxEvents: 150, + maxStats: 25, + } + + go stats.eventWorker(0) + go stats.eventWorker(1) + go stats.eventWorker(2) + go stats.eventWorker(3) + + return stats +} + +// SetConfig configures the max events to keep in the backlog before sending +// the stats to the UI, or while the UI is not connected. +// if the backlog is full, it'll be shifted by one. +func (s *Statistics) SetConfig(config StatsConfig) { + if config.MaxEvents > 0 { + s.maxEvents = config.MaxEvents + } + if config.MaxStats > 0 { + s.maxStats = config.MaxStats + } +} + +// OnDNSResponse increases the counter of dns and accepted connections. +func (s *Statistics) OnDNSResponse() { + s.Lock() + defer s.Unlock() + s.DNSResponses++ + s.Accepted++ +} + +// OnIgnored increases the counter of ignored and accepted connections. +func (s *Statistics) OnIgnored() { + s.Lock() + defer s.Unlock() + s.Ignored++ + s.Accepted++ +} + +func (s *Statistics) incMap(m *map[string]uint64, key string) { + if val, found := (*m)[key]; found == false { + // do we have enough space left? + nElems := len(*m) + if nElems >= s.maxStats { + // find the element with less hits + nMin := uint64(9999999999) + minKey := "" + for k, v := range *m { + if v < nMin { + minKey = k + nMin = v + } + } + // remove it + if minKey != "" { + delete(*m, minKey) + } + } + + (*m)[key] = 1 + } else { + (*m)[key] = val + 1 + } +} + +func (s *Statistics) eventWorker(id int) { + log.Debug("Stats worker #%d started.", id) + + for true { + select { + case job := <-s.jobs: + s.onConnection(job.con, job.match, job.wasMissed) + } + } +} + +func (s *Statistics) onConnection(con *conman.Connection, match *rule.Rule, wasMissed bool) { + s.Lock() + defer s.Unlock() + + s.Connections++ + + if wasMissed { + s.RuleMisses++ + } else { + s.RuleHits++ + } + + if wasMissed == false && match.Action == rule.Allow { + s.Accepted++ + } else { + s.Dropped++ + } + + s.incMap(&s.ByProto, con.Protocol) + s.incMap(&s.ByAddress, con.DstIP.String()) + if con.DstHost != "" { + s.incMap(&s.ByHost, con.DstHost) + } + s.incMap(&s.ByPort, fmt.Sprintf("%d", con.DstPort)) + s.incMap(&s.ByUID, fmt.Sprintf("%d", con.Entry.UserId)) + s.incMap(&s.ByExecutable, con.Process.Path) + + // if we reached the limit, shift everything back + // by one position + nEvents := len(s.Events) + if nEvents == s.maxEvents { + s.Events = s.Events[1:] + } + if wasMissed { + return + } + s.Events = append(s.Events, NewEvent(con, match)) +} + +// OnConnectionEvent sends the details of a new connection throughout a channel, +// in order to add the connection to the stats. +func (s *Statistics) OnConnectionEvent(con *conman.Connection, match *rule.Rule, wasMissed bool) { + s.jobs <- conEvent{ + con: con, + match: match, + wasMissed: wasMissed, + } +} + +func (s *Statistics) serializeEvents() []*protocol.Event { + nEvents := len(s.Events) + serialized := make([]*protocol.Event, nEvents) + + for i, e := range s.Events { + serialized[i] = e.Serialize() + } + + return serialized +} + +// emptyStats empties the stats once we've sent them to the GUI. +// We don't need them anymore here. +func (s *Statistics) emptyStats() { + s.Lock() + if len(s.Events) > 0 { + s.Events = make([]*Event, 0) + } + s.Unlock() +} + +// Serialize returns the collected statistics. +// After return the stats, the Events are emptied, to keep collecting more stats +// and not miss connections. +func (s *Statistics) Serialize() *protocol.Statistics { + s.Lock() + defer s.emptyStats() + defer s.Unlock() + + return &protocol.Statistics{ + DaemonVersion: core.Version, + Rules: uint64(s.rules.NumRules()), + Uptime: uint64(time.Since(s.Started).Seconds()), + DnsResponses: uint64(s.DNSResponses), + Connections: uint64(s.Connections), + Ignored: uint64(s.Ignored), + Accepted: uint64(s.Accepted), + Dropped: uint64(s.Dropped), + RuleHits: uint64(s.RuleHits), + RuleMisses: uint64(s.RuleMisses), + Events: s.serializeEvents(), + ByProto: s.ByProto, + ByAddress: s.ByAddress, + ByHost: s.ByHost, + ByPort: s.ByPort, + ByUid: s.ByUID, + ByExecutable: s.ByExecutable, + } +} diff --git a/daemon/system-fw.json b/daemon/system-fw.json new file mode 100644 index 0000000..400f6e2 --- /dev/null +++ b/daemon/system-fw.json @@ -0,0 +1,14 @@ +{ + "SystemRules": [ + { + "Rule": { + "Description": "Allow icmp", + "Table": "mangle", + "Chain": "OUTPUT", + "Parameters": "-p icmp", + "Target": "ACCEPT", + "TargetParameters": "" + } + } + ] +} diff --git a/daemon/ui/client.go b/daemon/ui/client.go new file mode 100644 index 0000000..010a0bb --- /dev/null +++ b/daemon/ui/client.go @@ -0,0 +1,343 @@ +package ui + +import ( + "fmt" + "net" + "sync" + "time" + + "github.com/evilsocket/opensnitch/daemon/conman" + "github.com/evilsocket/opensnitch/daemon/firewall/iptables" + "github.com/evilsocket/opensnitch/daemon/log" + "github.com/evilsocket/opensnitch/daemon/rule" + "github.com/evilsocket/opensnitch/daemon/statistics" + "github.com/evilsocket/opensnitch/daemon/ui/protocol" + + "github.com/fsnotify/fsnotify" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/connectivity" + "google.golang.org/grpc/keepalive" +) + +var ( + configFile = "/etc/opensnitchd/default-config.json" + dummyOperator, _ = rule.NewOperator(rule.Simple, false, rule.OpTrue, "", make([]rule.Operator, 0)) + clientDisconnectedRule = rule.Create("ui.client.disconnected", true, false, rule.Allow, rule.Once, dummyOperator) + // While the GUI is connected, deny by default everything until the user takes an action. + clientConnectedRule = rule.Create("ui.client.connected", true, false, rule.Deny, rule.Once, dummyOperator) + clientErrorRule = rule.Create("ui.client.error", true, false, rule.Allow, rule.Once, dummyOperator) + config Config +) + +type serverConfig struct { + Address string `json:"Address"` + LogFile string `json:"LogFile"` +} + +// Config holds the values loaded from configFile +type Config struct { + sync.RWMutex + Server serverConfig `json:"Server"` + DefaultAction string `json:"DefaultAction"` + DefaultDuration string `json:"DefaultDuration"` + InterceptUnknown bool `json:"InterceptUnknown"` + ProcMonitorMethod string `json:"ProcMonitorMethod"` + LogLevel *uint32 `json:"LogLevel"` + Firewall string `json:"Firewall"` + Stats statistics.StatsConfig `json:"Stats"` +} + +// Client holds the connection information of a client. +type Client struct { + sync.RWMutex + clientCtx context.Context + clientCancel context.CancelFunc + + stats *statistics.Statistics + rules *rule.Loader + socketPath string + isUnixSocket bool + con *grpc.ClientConn + client protocol.UIClient + configWatcher *fsnotify.Watcher + streamNotifications protocol.UI_NotificationsClient + //isAsking is set to true if the client is awaiting a decision from the GUI + isAsking bool +} + +// NewClient creates and configures a new client. +func NewClient(socketPath string, stats *statistics.Statistics, rules *rule.Loader) *Client { + c := &Client{ + stats: stats, + rules: rules, + isUnixSocket: false, + isAsking: false, + } + c.clientCtx, c.clientCancel = context.WithCancel(context.Background()) + + if watcher, err := fsnotify.NewWatcher(); err == nil { + c.configWatcher = watcher + } + c.loadDiskConfiguration(false) + if socketPath != "" { + c.setSocketPath(c.getSocketPath(socketPath)) + } + + go c.poller() + return c +} + +// Close cancels the running tasks: pinging the server and (re)connection poller. +func (c *Client) Close() { + c.clientCancel() +} + +// ProcMonitorMethod returns the monitor method configured. +// If it's not present in the config file, it'll return an empty string. +func (c *Client) ProcMonitorMethod() string { + config.RLock() + defer config.RUnlock() + return config.ProcMonitorMethod +} + +// InterceptUnknown returns +func (c *Client) InterceptUnknown() bool { + config.RLock() + defer config.RUnlock() + return config.InterceptUnknown +} + +// GetStatsConfig returns the stats config from disk +func (c *Client) GetStatsConfig() statistics.StatsConfig { + config.RLock() + defer config.RUnlock() + return config.Stats +} + +// GetFirewallType returns the firewall to use +func (c *Client) GetFirewallType() string { + config.RLock() + defer config.RUnlock() + if config.Firewall == "" { + return iptables.Name + } + return config.Firewall +} + +// DefaultAction returns the default configured action for +func (c *Client) DefaultAction() rule.Action { + isConnected := c.Connected() + + c.RLock() + defer c.RUnlock() + + if isConnected { + return clientConnectedRule.Action + } + + return clientDisconnectedRule.Action +} + +// DefaultDuration returns the default duration configured for a rule. +// For example it can be: once, always, "until restart". +func (c *Client) DefaultDuration() rule.Duration { + c.RLock() + defer c.RUnlock() + return clientDisconnectedRule.Duration +} + +// Connected checks if the client has established a connection with the server. +func (c *Client) Connected() bool { + c.RLock() + defer c.RUnlock() + if c.con == nil || c.con.GetState() != connectivity.Ready { + return false + } + return true +} + +//GetIsAsking returns the isAsking flag +func (c *Client) GetIsAsking() bool { + c.RLock() + defer c.RUnlock() + return c.isAsking +} + +//SetIsAsking sets the isAsking flag +func (c *Client) SetIsAsking(flag bool) { + c.Lock() + defer c.Unlock() + c.isAsking = flag +} + +func (c *Client) poller() { + log.Debug("UI service poller started for socket %s", c.socketPath) + wasConnected := false + for { + select { + case <-c.clientCtx.Done(): + log.Info("Client.poller() exit, Done()") + goto Exit + default: + isConnected := c.Connected() + if wasConnected != isConnected { + c.onStatusChange(isConnected) + wasConnected = isConnected + } + + if c.Connected() == false { + // connect and create the client if needed + if err := c.connect(); err != nil { + log.Warning("Error while connecting to UI service: %s", err) + } + } + if c.Connected() == true { + // if the client is connected and ready, send a ping + if err := c.ping(time.Now()); err != nil { + log.Warning("Error while pinging UI service: %s, state: %v", err, c.con.GetState()) + } + } + + time.Sleep(1 * time.Second) + } + } +Exit: + log.Info("uiClient exit") +} + +func (c *Client) onStatusChange(connected bool) { + if connected { + log.Info("Connected to the UI service on %s", c.socketPath) + go c.Subscribe() + } else { + log.Error("Connection to the UI service lost.") + c.disconnect() + } +} + +func (c *Client) connect() (err error) { + if c.Connected() { + return + } + + if c.con != nil { + if c.con.GetState() == connectivity.TransientFailure || c.con.GetState() == connectivity.Shutdown { + c.disconnect() + } else { + return + } + } + + if err := c.openSocket(); err != nil { + c.disconnect() + return err + } + + if c.client == nil { + c.client = protocol.NewUIClient(c.con) + } + return nil +} + +func (c *Client) openSocket() (err error) { + c.Lock() + defer c.Unlock() + + if c.isUnixSocket { + c.con, err = grpc.Dial(c.socketPath, grpc.WithInsecure(), + grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) { + return net.DialTimeout("unix", addr, timeout) + })) + } else { + // https://pkg.go.dev/google.golang.org/grpc/keepalive#ClientParameters + var kacp = keepalive.ClientParameters{ + Time: 5 * time.Second, + // if there's no activity after ^, wait 20s and close + // server timeout is 20s by default. + Timeout: 22 * time.Second, + // send pings even without active streams + PermitWithoutStream: true, + } + + c.con, err = grpc.Dial(c.socketPath, grpc.WithInsecure(), grpc.WithKeepaliveParams(kacp)) + } + + return err +} + +func (c *Client) disconnect() { + c.Lock() + defer c.Unlock() + + c.client = nil + if c.con != nil { + c.con.Close() + c.con = nil + log.Debug("client.disconnect()") + } +} + +func (c *Client) ping(ts time.Time) (err error) { + if c.Connected() == false { + return fmt.Errorf("service is not connected") + } + + c.Lock() + defer c.Unlock() + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + reqID := uint64(ts.UnixNano()) + + pReq := &protocol.PingRequest{ + Id: reqID, + Stats: c.stats.Serialize(), + } + c.stats.RLock() + pong, err := c.client.Ping(ctx, pReq) + c.stats.RUnlock() + if err != nil { + return err + } + + if pong.Id != reqID { + return fmt.Errorf("Expected pong with id 0x%x, got 0x%x", reqID, pong.Id) + } + + return nil +} + +// Ask sends a request to the server, with the values of a connection to be +// allowed or denied. +func (c *Client) Ask(con *conman.Connection) *rule.Rule { + if c.client == nil { + return nil + } + + // FIXME: if timeout is fired, the rule is not added to the list in the GUI + ctx, cancel := context.WithTimeout(context.Background(), time.Second*120) + defer cancel() + reply, err := c.client.AskRule(ctx, con.Serialize()) + if err != nil { + log.Warning("Error while asking for rule: %s - %v", err, con) + return nil + } + + r, err := rule.Deserialize(reply) + if err != nil { + return nil + } + return r +} + +func (c *Client) monitorConfigWorker() { + for { + select { + case event := <-c.configWatcher.Events: + if (event.Op&fsnotify.Write == fsnotify.Write) || (event.Op&fsnotify.Remove == fsnotify.Remove) { + c.loadDiskConfiguration(true) + } + } + } +} diff --git a/daemon/ui/config.go b/daemon/ui/config.go new file mode 100644 index 0000000..a27bb46 --- /dev/null +++ b/daemon/ui/config.go @@ -0,0 +1,118 @@ +package ui + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "strings" + + "github.com/evilsocket/opensnitch/daemon/log" + "github.com/evilsocket/opensnitch/daemon/procmon/monitor" + "github.com/evilsocket/opensnitch/daemon/rule" +) + +func (c *Client) getSocketPath(socketPath string) string { + c.Lock() + defer c.Unlock() + + if strings.HasPrefix(socketPath, "unix://") == true { + c.isUnixSocket = true + return socketPath[7:] + } + + c.isUnixSocket = false + return socketPath +} + +func (c *Client) setSocketPath(socketPath string) { + c.Lock() + defer c.Unlock() + + c.socketPath = socketPath +} + +func (c *Client) isProcMonitorEqual(newMonitorMethod string) bool { + config.RLock() + defer config.RUnlock() + + return newMonitorMethod == config.ProcMonitorMethod +} + +func (c *Client) parseConf(rawConfig string) (conf Config, err error) { + err = json.Unmarshal([]byte(rawConfig), &conf) + return conf, err +} + +func (c *Client) loadDiskConfiguration(reload bool) { + raw, err := ioutil.ReadFile(configFile) + if err != nil { + fmt.Errorf("Error loading disk configuration %s: %s", configFile, err) + } + + if ok := c.loadConfiguration(raw); ok { + if err := c.configWatcher.Add(configFile); err != nil { + log.Error("Could not watch path: %s", err) + return + } + } + + if reload { + return + } + + go c.monitorConfigWorker() +} + +func (c *Client) loadConfiguration(rawConfig []byte) bool { + config.Lock() + defer config.Unlock() + + if err := json.Unmarshal(rawConfig, &config); err != nil { + log.Error("Error parsing configuration %s: %s", configFile, err) + return false + } + // firstly load config level, to detect further errors if any + if config.LogLevel != nil { + log.SetLogLevel(int(*config.LogLevel)) + } + if config.Server.LogFile != "" { + log.Close() + log.OpenFile(config.Server.LogFile) + } + + if config.Server.Address != "" { + tempSocketPath := c.getSocketPath(config.Server.Address) + if tempSocketPath != c.socketPath { + // disconnect, and let the connection poller reconnect to the new address + c.disconnect() + } + c.setSocketPath(tempSocketPath) + } + if config.DefaultAction != "" { + clientDisconnectedRule.Action = rule.Action(config.DefaultAction) + clientErrorRule.Action = rule.Action(config.DefaultAction) + } + if config.DefaultDuration != "" { + clientDisconnectedRule.Duration = rule.Duration(config.DefaultDuration) + clientErrorRule.Duration = rule.Duration(config.DefaultDuration) + } + if config.ProcMonitorMethod != "" { + if err := monitor.ReconfigureMonitorMethod(config.ProcMonitorMethod); err != nil { + log.Warning("Unable to set new process monitor method from disk: %v", err) + } + } + + return true +} + +func (c *Client) saveConfiguration(rawConfig string) (err error) { + if c.loadConfiguration([]byte(rawConfig)) != true { + return fmt.Errorf("Error parsing configuration %s: %s", rawConfig, err) + } + + if err = ioutil.WriteFile(configFile, []byte(rawConfig), 0644); err != nil { + log.Error("writing configuration to disk: %s", err) + return err + } + return nil +} diff --git a/daemon/ui/notifications.go b/daemon/ui/notifications.go new file mode 100644 index 0000000..27fd929 --- /dev/null +++ b/daemon/ui/notifications.go @@ -0,0 +1,304 @@ +package ui + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "strconv" + "strings" + "time" + + "github.com/evilsocket/opensnitch/daemon/core" + "github.com/evilsocket/opensnitch/daemon/firewall" + "github.com/evilsocket/opensnitch/daemon/log" + "github.com/evilsocket/opensnitch/daemon/procmon" + "github.com/evilsocket/opensnitch/daemon/procmon/monitor" + "github.com/evilsocket/opensnitch/daemon/rule" + "github.com/evilsocket/opensnitch/daemon/ui/protocol" + "golang.org/x/net/context" +) + +var stopMonitoringProcess = make(chan int) + +// NewReply constructs a new protocol notification reply +func NewReply(rID uint64, replyCode protocol.NotificationReplyCode, data string) *protocol.NotificationReply { + return &protocol.NotificationReply{ + Id: rID, + Code: replyCode, + Data: data, + } +} + +func (c *Client) getClientConfig() *protocol.ClientConfig { + raw, _ := ioutil.ReadFile(configFile) + nodeName := core.GetHostname() + nodeVersion := core.GetKernelVersion() + var ts time.Time + rulesTotal := len(c.rules.GetAll()) + ruleList := make([]*protocol.Rule, rulesTotal) + idx := 0 + for _, r := range c.rules.GetAll() { + ruleList[idx] = r.Serialize() + idx++ + } + return &protocol.ClientConfig{ + Id: uint64(ts.UnixNano()), + Name: nodeName, + Version: nodeVersion, + IsFirewallRunning: firewall.IsRunning(), + Config: strings.Replace(string(raw), "\n", "", -1), + LogLevel: uint32(log.MinLevel), + Rules: ruleList, + } +} + +func (c *Client) monitorProcessDetails(pid int, stream protocol.UI_NotificationsClient, notification *protocol.Notification) { + p := procmon.NewProcess(pid, "") + ticker := time.NewTicker(2 * time.Second) + + for { + select { + case _pid := <-stopMonitoringProcess: + if _pid != pid { + continue + } + goto Exit + case <-ticker.C: + if err := p.GetInfo(); err != nil { + c.sendNotificationReply(stream, notification.Id, notification.Data, err) + goto Exit + } + + pJSON, err := json.Marshal(p) + notification.Data = string(pJSON) + if errs := c.sendNotificationReply(stream, notification.Id, notification.Data, err); errs != nil { + goto Exit + } + } + } + +Exit: + ticker.Stop() +} + +func (c *Client) handleActionChangeConfig(stream protocol.UI_NotificationsClient, notification *protocol.Notification) { + log.Info("[notification] Reloading configuration") + // Parse received configuration first, to get the new proc monitor method. + newConf, err := c.parseConf(notification.Data) + if err != nil { + log.Warning("[notification] error parsing received config: %v", notification.Data) + c.sendNotificationReply(stream, notification.Id, "", err) + return + } + + if err := monitor.ReconfigureMonitorMethod(newConf.ProcMonitorMethod); err != nil { + c.sendNotificationReply(stream, notification.Id, "", err) + return + } + + // this save operation triggers a re-loadConfiguration() + err = c.saveConfiguration(notification.Data) + if err != nil { + log.Warning("[notification] CHANGE_CONFIG not applied %s", err) + } + + c.sendNotificationReply(stream, notification.Id, "", err) +} + +func (c *Client) handleActionEnableRule(stream protocol.UI_NotificationsClient, notification *protocol.Notification) { + var err error + for _, rul := range notification.Rules { + log.Info("[notification] enable rule: %s", rul.Name) + // protocol.Rule(protobuf) != rule.Rule(json) + r, _ := rule.Deserialize(rul) + r.Enabled = true + // save to disk only if the duration is rule.Always + err = c.rules.Replace(r, r.Duration == rule.Always) + } + c.sendNotificationReply(stream, notification.Id, "", err) +} + +func (c *Client) handleActionDisableRule(stream protocol.UI_NotificationsClient, notification *protocol.Notification) { + var err error + for _, rul := range notification.Rules { + log.Info("[notification] disable rule: %s", rul) + r, _ := rule.Deserialize(rul) + r.Enabled = false + err = c.rules.Replace(r, r.Duration == rule.Always) + } + c.sendNotificationReply(stream, notification.Id, "", err) +} + +func (c *Client) handleActionChangeRule(stream protocol.UI_NotificationsClient, notification *protocol.Notification) { + var rErr error + for _, rul := range notification.Rules { + r, err := rule.Deserialize(rul) + if r == nil { + rErr = fmt.Errorf("Invalid rule, %s", err) + continue + } + log.Info("[notification] change rule: %s %d", r, notification.Id) + if err := c.rules.Replace(r, r.Duration == rule.Always); err != nil { + log.Warning("[notification] Error changing rule: %s %s", err, r) + rErr = err + } + } + c.sendNotificationReply(stream, notification.Id, "", rErr) +} + +func (c *Client) handleActionDeleteRule(stream protocol.UI_NotificationsClient, notification *protocol.Notification) { + var err error + for _, rul := range notification.Rules { + log.Info("[notification] delete rule: %s %d", rul.Name, notification.Id) + err = c.rules.Delete(rul.Name) + if err != nil { + log.Error("[notification] Error deleting rule: %s %s", err, rul) + } + } + c.sendNotificationReply(stream, notification.Id, "", err) +} + +func (c *Client) handleActionMonitorProcess(stream protocol.UI_NotificationsClient, notification *protocol.Notification) { + pid, err := strconv.Atoi(notification.Data) + if err != nil { + log.Error("parsing PID to monitor: %d, err: %s", pid, err) + return + } + if !core.Exists(fmt.Sprint("/proc/", pid)) { + c.sendNotificationReply(stream, notification.Id, "", fmt.Errorf("The process is no longer running")) + return + } + go c.monitorProcessDetails(pid, stream, notification) +} + +func (c *Client) handleActionStopMonitorProcess(stream protocol.UI_NotificationsClient, notification *protocol.Notification) { + pid, err := strconv.Atoi(notification.Data) + if err != nil { + log.Error("parsing PID to stop monitor: %d, err: %s", pid, err) + c.sendNotificationReply(stream, notification.Id, "", fmt.Errorf("Error stopping monitor: %s", notification.Data)) + return + } + stopMonitoringProcess <- pid + c.sendNotificationReply(stream, notification.Id, "", nil) +} + +func (c *Client) handleNotification(stream protocol.UI_NotificationsClient, notification *protocol.Notification) { + switch { + case notification.Type == protocol.Action_MONITOR_PROCESS: + c.handleActionMonitorProcess(stream, notification) + + case notification.Type == protocol.Action_STOP_MONITOR_PROCESS: + c.handleActionStopMonitorProcess(stream, notification) + + case notification.Type == protocol.Action_CHANGE_CONFIG: + c.handleActionChangeConfig(stream, notification) + + case notification.Type == protocol.Action_LOAD_FIREWALL: + log.Info("[notification] starting firewall") + firewall.Init(c.GetFirewallType(), nil) + c.sendNotificationReply(stream, notification.Id, "", nil) + + case notification.Type == protocol.Action_UNLOAD_FIREWALL: + log.Info("[notification] stopping firewall") + firewall.Stop() + c.sendNotificationReply(stream, notification.Id, "", nil) + + // ENABLE_RULE just replaces the rule on disk + case notification.Type == protocol.Action_ENABLE_RULE: + c.handleActionEnableRule(stream, notification) + + case notification.Type == protocol.Action_DISABLE_RULE: + c.handleActionDisableRule(stream, notification) + + case notification.Type == protocol.Action_DELETE_RULE: + c.handleActionDeleteRule(stream, notification) + + // CHANGE_RULE can add() or replace) an existing rule. + case notification.Type == protocol.Action_CHANGE_RULE: + c.handleActionChangeRule(stream, notification) + } +} + +func (c *Client) sendNotificationReply(stream protocol.UI_NotificationsClient, nID uint64, data string, err error) error { + reply := NewReply(nID, protocol.NotificationReplyCode_OK, data) + if err != nil { + reply.Code = protocol.NotificationReplyCode_ERROR + reply.Data = fmt.Sprint(err) + } + if err := stream.Send(reply); err != nil { + log.Error("Error replying to notification: %s %d", err, reply.Id) + return err + } + + return nil +} + +// Subscribe opens a connection with the server (UI), to start +// receiving notifications. +// It firstly sends the daemon status and configuration. +func (c *Client) Subscribe() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + + clientCfg, err := c.client.Subscribe(ctx, c.getClientConfig()) + if err != nil { + log.Error("Subscribing to GUI %s", err) + // When connecting to the GUI via TCP, sometimes the notifications channel is + // not established, and the main channel is never closed. + // We need to disconnect everything after a timeout and try it again. + c.disconnect() + return + } + + if tempConf, err := c.parseConf(clientCfg.Config); err == nil { + c.Lock() + clientConnectedRule.Action = rule.Action(tempConf.DefaultAction) + c.Unlock() + } + c.listenForNotifications() +} + +// Notifications is the channel where the daemon receives messages from the server. +// It consists of 2 grpc streams (send/receive) that are never closed, +// this way we can share messages in realtime. +// If the GUI is closed, we'll receive an error reading from the channel. +func (c *Client) listenForNotifications() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // open the stream channel + streamReply := &protocol.NotificationReply{Id: 0, Code: protocol.NotificationReplyCode_OK} + notisStream, err := c.client.Notifications(ctx) + if err != nil { + log.Error("establishing notifications channel %s", err) + return + } + // send the first notification + if err := notisStream.Send(streamReply); err != nil { + log.Error("sending notification HELLO %s", err) + return + } + log.Info("Start receiving notifications") + for { + select { + case <-c.clientCtx.Done(): + goto Exit + default: + noti, err := notisStream.Recv() + if err == io.EOF { + log.Warning("notification channel closed by the server") + goto Exit + } + if err != nil { + log.Error("getting notifications: %s %s", err, noti) + goto Exit + } + c.handleNotification(notisStream, noti) + } + } +Exit: + notisStream.CloseSend() + log.Info("Stop receiving notifications") + c.disconnect() +} diff --git a/daemon/ui/protocol/.gitkeep b/daemon/ui/protocol/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..d231c03 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,233 @@ +opensnitch (1.5.5-1) unstable; urgency=medium + + * New upstream release. + * Bump Standards-Version to 4.6.2. + * Upload sponsored by Petter Reinholdtsen. + + -- Gustavo Iñiguez Goya <gustavo.iniguez.goya@gmail.com> Wed, 01 Feb 2023 22:37:12 +0100 + +opensnitch (1.5.4-1) unstable; urgency=high + + * New upstream release. (Closes: #1030115) + * debian/control: + - Updated packages description. + - Removed debconf and whiptail|dialog dependencies. + - Added xdg-user-dirs, gtk-update-icon-cache dependencies. + - Point Vcs-Git field to the 1.5.0 branch. + * debian/postinst: + - Fixed opensnitch_ui.desktop installation. + - Fixed updating icons cache. + * debian/postrm: + - Fixed removing opensnitch_ui.desktop + * debian/tests/: + - Added autopkgtests. + * Upload sponsored by Petter Reinholdtsen. + + -- Gustavo Iñiguez Goya <gustavo.iniguez.goya@gmail.com> Tue, 31 Jan 2023 23:48:58 +0100 + +opensnitch (1.5.3-1) unstable; urgency=medium + + * Added debian/upstream/metadata. + * Updated Homepage url. + * Updated Copyright years. + + -- Gustavo-Iniguez-Goya <gustavo.iniguez.goya@gmail.com> Sun, 22 Jan 2023 21:30:45 +0100 + +opensnitch (1.5.2.1-1) unstable; urgency=medium + + * Initial release. (Closes: #909567) + + -- Gustavo-Iniguez-Goya <gustavo.iniguez.goya@gmail.com> Fri, 20 Jan 2023 22:26:40 +0000 + +opensnitch (1.5.2-1) unstable; urgency=medium + + * try to mount debugfs on boot up + + -- gustavo-iniguez-goya <gustavo.iniguez.goya@gmail.com> Wed, 27 Jul 2022 17:29:33 +0200 + +opensnitch (1.5.1-1) unstable; urgency=medium + + * Better eBPF cache. + * Fixed error resolving domains to localhost. + * Fixed error deleting our nftables rules. + + -- gustavo-iniguez-goya <gustavo.iniguez.goya@gmail.com> Fri, 25 Feb 2022 01:21:38 +0100 + +opensnitch (1.5.0-1) unstable; urgency=medium + + * New release. + * Added Reject option. + * New lists types to block ads/malware/... + * Better connections interception. + * Better VPNs handling. + * Bug fixes. + + -- gustavo-iniguez-goya <gustavo.iniguez.goya@gmail.com> Fri, 28 Jan 2022 23:20:38 +0100 + +opensnitch (1.5.0~rc2-1) unstable; urgency=medium + + * Better connections interception. + * Improvements. + + -- gustavo-iniguez-goya <gustavo.iniguez.goya@gmail.com> Sun, 16 Jan 2022 23:15:12 +0100 + +opensnitch (1.5.0~rc1-1) unstable; urgency=medium + + * New features. + + -- gustavo-iniguez-goya <gustavo.iniguez.goya@gmail.com> Thu, 07 Oct 2021 14:57:35 +0200 + +opensnitch (1.4.0-1) unstable; urgency=medium + + * final release. + + -- gustavo-iniguez-goya <gustavo.iniguez.goya@gmail.com> Fri, 27 Aug 2021 13:33:07 +0200 + +opensnitch (1.4.0~rc4-1) unstable; urgency=medium + + * Bug fix release. + + -- gustavo-iniguez-goya <gooffy1@gmail.com> Wed, 11 Aug 2021 15:17:49 +0200 + +opensnitch (1.4.0~rc3-1) unstable; urgency=medium + + * Bug fix release. + + -- gustavo-iniguez-goya <gooffy1@gmail.com> Fri, 16 Jul 2021 23:28:52 +0200 + +opensnitch (1.4.0~rc2-1) unstable; urgency=medium + + * Added eBPF support. + * Fixes and improvements. + + -- gustavo-iniguez-goya <gooffy1@gmail.com> Fri, 07 May 2021 01:08:02 +0200 + +opensnitch (1.4.0~rc-1) unstable; urgency=medium + + * Bug fix and improvements release. + + -- gustavo-iniguez-goya <gooffy1@gmail.com> Thu, 25 Mar 2021 01:02:31 +0100 + +opensnitch (1.3.6-1) unstable; urgency=medium + + * Bug fix and improvements release. + + -- gustavo-iniguez-goya <gooffy1@gmail.com> Wed, 10 Feb 2021 10:17:43 +0100 + +opensnitch (1.3.5-1) unstable; urgency=medium + + * Bug fix and improvements release. + + -- gustavo-iniguez-goya <gooffy1@gmail.com> Mon, 11 Jan 2021 18:01:53 +0100 + +opensnitch (1.3.0-1) unstable; urgency=medium + + * Fixed how we check rules + * Fixed cpu spike after disable interception. + * Fixed cleaning up fw rules on exit. + * make regexp rules case-insensitive by default + * allow to filter by dst network. + + -- gustavo-iniguez-goya <gooffy1@gmail.com> Wed, 16 Dec 2020 01:15:03 +0100 + +opensnitch (1.3.0~rc-1) unstable; urgency=medium + + * Non-maintainer upload. + + -- gustavo-iniguez-goya <gooffy1@gmail.com> Fri, 13 Nov 2020 00:51:34 +0100 + +opensnitch (1.2.0-1) unstable; urgency=medium + + * Fixed memleaks. + * Sort rules by name + * Added priority field to rules. + * Other fixes + + -- gustavo-iniguez-goya <gooffy1@gmail.com> Mon, 09 Nov 2020 22:55:13 +0100 + +opensnitch (1.0.1-1) unstable; urgency=medium + + * Fixed app exit when IPv6 is not supported. + * Other fixes. + + -- gustavo-iniguez-goya <gooffy1@gmail.com> Thu, 30 Jul 2020 21:56:20 +0200 + +opensnitch (1.0.0-1) unstable; urgency=medium + + * v1.0.0 released. + + -- gustavo-iniguez-goya <gooffy1@gmail.com> Thu, 16 Jul 2020 00:19:26 +0200 + +opensnitch (1.0.0rc11-1) unstable; urgency=medium + + * Fixed multiple race conditions. + * Fixed CWD parsing when using audit proc monitor method. + + -- gustavo-iniguez-goya <gooffy1@gmail.com> Wed, 24 Jun 2020 00:10:38 +0200 + +opensnitch (1.0.0rc10-1) unstable; urgency=medium + + * Fixed checking UID functions availability. + * Improved process path parsing. + * Fixed applying config from the UI. + * Fixed default log level. + * Gather CWD and process environment vars. + * Increase default timeout when asking for a rule. + + -- gustavo-iniguez-goya <gooffy1@gmail.com> Sat, 13 Jun 2020 18:45:02 +0200 + +opensnitch (1.0.0rc9-1) unstable; urgency=medium + + * Ignore malformed rules from loading. + * Allow to modify and add rules from the UI. + + -- gustavo-iniguez-goya <gooffy1@gmail.com> Sun, 17 May 2020 18:18:24 +0200 + +opensnitch (1.0.0rc8) unstable; urgency=medium + + * Allow to change settings from the UI. + * Improved connection handling with the UI. + + -- gustavo-iniguez-goya <gooffy1@gmail.com> Wed, 29 Apr 2020 21:52:27 +0200 + +opensnitch (1.0.0rc7-1) unstable; urgency=medium + + * Stability, performance and realiability improvements. + + -- gustavo-iniguez-goya <gooffy1@gmail.com> Sun, 12 Apr 2020 23:25:41 +0200 + +opensnitch (1.0.0rc6-1) unstable; urgency=medium + + * Fixed iptables rules deletion. + * Improved PIDs cache. + * Added audit process monitoring method. + * Added logrotate file. + * Added default configuration file. + + -- gustavo-iniguez-goya <gooffy1@gmail.com> Sun, 08 Mar 2020 20:47:58 +0100 + +opensnitch (1.0.0rc-5) unstable; urgency=medium + + * Fixed netlink socket querying. + * Added check to reload firewall rules if missing. + + -- gustavo-iniguez-goya <gooffy1@gmail.com> Mon, 24 Feb 2020 19:55:06 +0100 + +opensnitch (1.0.0rc-3) unstable; urgency=medium + + * @see: https://github.com/gustavo-iniguez-goya/opensnitch/releases + + -- gustavo-iniguez-goya <gooffy1@gmail.com> Tue, 18 Feb 2020 10:09:45 +0100 + +opensnitch (1.0.0rc-2) unstable; urgency=medium + + * UI minor changes + * Expand deb package compatibility. + + -- gustavo-iniguez-goya <gooffy1@gmail.com> Wed, 05 Feb 2020 21:50:20 +0100 + +opensnitch (1.0.0rc-1) unstable; urgency=medium + + * Initial release + + -- gustavo-iniguez-goya <gooffy1@gmail.com> Fri, 22 Nov 2019 01:14:08 +0100 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..f67967b --- /dev/null +++ b/debian/control @@ -0,0 +1,95 @@ +Source: opensnitch +Maintainer: Gustavo Iñiguez Goya <gustavo.iniguez.goya@gmail.com> +Section: devel +Testsuite: autopkgtest-pkg-go +Priority: optional +Build-Depends: + debhelper-compat (= 11), + dh-golang, + dh-python, + golang-any, + golang-github-evilsocket-ftrace-dev, + golang-github-fsnotify-fsnotify-dev, + golang-github-google-gopacket-dev, + golang-github-google-nftables-dev, + golang-github-iovisor-gobpf-dev, + golang-github-vishvananda-netlink-dev, + golang-golang-x-net-dev, + golang-google-grpc-dev, + golang-goprotobuf-dev, + libmnl-dev, + libnetfilter-queue-dev, + pkg-config, + protoc-gen-go-grpc, + pyqt5-dev-tools, + qttools5-dev-tools, + python3-all, + python3-grpc-tools, + python3-setuptools +Standards-Version: 4.6.2 +Vcs-Browser: https://github.com/evilsocket/opensnitch +Vcs-Git: https://github.com/evilsocket/opensnitch.git -b 1.5.0 +Homepage: https://github.com/evilsocket/opensnitch +Rules-Requires-Root: no +XS-Go-Import-Path: github.com/evilsocket/opensnitch + +Package: opensnitch +Section: net +Architecture: any +Depends: + ${misc:Depends}, + ${shlibs:Depends}, +Recommends: python3-opensnitch-ui +Built-Using: ${misc:Built-Using} +Description: GNU/Linux interactive application firewall + OpenSnitch is a GNU/Linux firewall application. + Whenever a program makes a connection, it'll prompt the user to allow or deny + it. + . + The user can decide if block the outgoing connection based on properties of + the connection: by port, by uid, by dst ip, by program or a combination + of them. + . + These rules can last forever, until the app restart or just one time. + . + The GUI allows the user to view live outgoing connections, as well as search + by process, user, host or port. + . + OpenSnitch can also work as a system-wide domains blocker, by using lists + of domains, list of IPs or list of regular expressions. + + +Package: python3-opensnitch-ui +Architecture: all +Section: net +Depends: + ${misc:Depends}, + ${shlibs:Depends}, + libqt5sql5-sqlite, + python3-grpcio, + python3-notify2, + python3-pyinotify, + python3-pyqt5, + python3-pyqt5.qtsql, + python3-setuptools, + python3-six, + python3-slugify, + python3:any, + xdg-user-dirs, + gtk-update-icon-cache +Recommends: + python3-pyasn +Suggests: opensnitch +Description: GNU/Linux interactive application firewall GUI + opensnitch-ui is a GUI for opensnitch written in Python. + It allows the user to view live outgoing connections, as well as search + for details of the intercepted connections. + . + The user can decide if block outgoing connections based on properties of + the connection: by port, by uid, by dst ip, by program or a combination + of them. + . + These rules can last forever, until restart the daemon or just one time. + . + OpenSnitch can also work as a system-wide domains blocker, by using lists + of domains, list of IPs or list of regular expressions. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..a867271 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,32 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Source: https://github.com/evilsocket/opensnitch +Upstream-Contact: Gustavo Iñiguez Goia <gooffy1@gmail.com> +Upstream-Name: opensnitch +Files-Excluded: + Godeps/_workspace + +Files: * +Copyright: + 2017-2018 evilsocket + 2019-2023 Gustavo Iñiguez Goia +Comment: Debian packaging is licensed under the same terms as upstream +License: GPL-3.0+ + This program is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later + version. + . + This program is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the GNU General Public License for more + details. + . + You should have received a copy of the GNU General Public + License along with this program. If not, If not, see + http://www.gnu.org/licenses/. + . + On Debian systems, the full text of the GNU General Public + License version 3 can be found in the file + '/usr/share/common-licenses/GPL-3'. diff --git a/debian/gbp.conf b/debian/gbp.conf new file mode 100644 index 0000000..cec628c --- /dev/null +++ b/debian/gbp.conf @@ -0,0 +1,2 @@ +[DEFAULT] +pristine-tar = True diff --git a/debian/gitlab-ci.yml b/debian/gitlab-ci.yml new file mode 100644 index 0000000..91ff7ea --- /dev/null +++ b/debian/gitlab-ci.yml @@ -0,0 +1,27 @@ +# auto-generated, DO NOT MODIFY. +# The authoritative copy of this file lives at: +# https://salsa.debian.org/go-team/ci/blob/master/config/gitlabciyml.go + +# TODO: publish under debian-go-team/ci +image: stapelberg/ci2 + +test_the_archive: + artifacts: + paths: + - before-applying-commit.json + - after-applying-commit.json + script: + # Create an overlay to discard writes to /srv/gopath/src after the build: + - "rm -rf /cache/overlay/{upper,work}" + - "mkdir -p /cache/overlay/{upper,work}" + - "mount -t overlay overlay -o lowerdir=/srv/gopath/src,upperdir=/cache/overlay/upper,workdir=/cache/overlay/work /srv/gopath/src" + - "export GOPATH=/srv/gopath" + - "export GOCACHE=/cache/go" + # Build the world as-is: + - "ci-build -exemptions=/var/lib/ci-build/exemptions.json > before-applying-commit.json" + # Copy this package into the overlay: + - "GBP_CONF_FILES=:debian/gbp.conf gbp buildpackage --git-no-pristine-tar --git-ignore-branch --git-ignore-new --git-export-dir=/tmp/export --git-no-overlay --git-tarball-dir=/nonexistant --git-cleaner=/bin/true --git-builder='dpkg-buildpackage -S -d --no-sign'" + - "pgt-gopath -dsc /tmp/export/*.dsc" + # Rebuild the world: + - "ci-build -exemptions=/var/lib/ci-build/exemptions.json > after-applying-commit.json" + - "ci-diff before-applying-commit.json after-applying-commit.json" diff --git a/debian/opensnitch.init b/debian/opensnitch.init new file mode 100644 index 0000000..77ce353 --- /dev/null +++ b/debian/opensnitch.init @@ -0,0 +1,78 @@ +#!/bin/sh + +### BEGIN INIT INFO +# Provides: opensnitchd +# Required-Start: $network $local_fs +# Required-Stop: $network $local_fs +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: opensnitchd daemon +# Description: opensnitch application firewall +### END INIT INFO + +NAME=opensnitchd +PIDDIR=/var/run/$NAME +OPENSNITCHDPID=$PIDDIR/$NAME.pid + +# clear conflicting settings from the environment +unset TMPDIR + +test -x /usr/bin/$NAME || exit 0 + +. /lib/lsb/init-functions + +case $1 in + start) + log_daemon_msg "Starting opensnitch daemon" $NAME + if [ ! -d /etc/$NAME/rules ]; then + mkdir -p /etc/$NAME/rules &>/dev/null + fi + + # Make sure we have our PIDDIR, even if it's on a tmpfs + install -o root -g root -m 755 -d $PIDDIR + + if ! start-stop-daemon --start --quiet --oknodo --pidfile $OPENSNITCHDPID --background --exec /usr/bin/$NAME -- -rules-path /etc/$NAME/rules; then + log_end_msg 1 + exit 1 + fi + + log_end_msg 0 + ;; + stop) + + log_daemon_msg "Stopping $NAME daemon" $NAME + + start-stop-daemon --stop --quiet --signal QUIT --name $NAME + # Wait a little and remove stale PID file + sleep 1 + if [ -f $OPENSNITCHDPID ] && ! ps h `cat $OPENSNITCHDPID` > /dev/null + then + rm -f $OPENSNITCHDPID + fi + + log_end_msg 0 + + ;; + reload) + log_daemon_msg "Reloading $NAME" $NAME + + start-stop-daemon --stop --quiet --signal HUP --pidfile $OPENSNITCHDPID + + log_end_msg 0 + ;; + restart|force-reload) + $0 stop + sleep 1 + $0 start + ;; + status) + status_of_proc /usr/bin/$NAME $NAME + exit $? + ;; + *) + echo "Usage: /etc/init.d/opensnitchd {start|stop|reload|restart|force-reload|status}" + exit 1 + ;; +esac + +exit 0 diff --git a/debian/opensnitch.install b/debian/opensnitch.install new file mode 100644 index 0000000..751664c --- /dev/null +++ b/debian/opensnitch.install @@ -0,0 +1,3 @@ +daemon/default-config.json etc/opensnitchd/ +daemon/system-fw.json etc/opensnitchd/ +#ebpf_prog/opensnitch.o etc/opensnitchd/ diff --git a/debian/opensnitch.logrotate b/debian/opensnitch.logrotate new file mode 100644 index 0000000..7e1d486 --- /dev/null +++ b/debian/opensnitch.logrotate @@ -0,0 +1,13 @@ +/var/log/opensnitchd.log { + rotate 7 +# order of the fields is important + maxsize 50M +# we need this option in order to keep logging + copytruncate + missingok + notifempty + delaycompress + compress + create 640 root root + weekly +} diff --git a/debian/opensnitch.service b/debian/opensnitch.service new file mode 100644 index 0000000..8d1b52f --- /dev/null +++ b/debian/opensnitch.service @@ -0,0 +1,16 @@ +[Unit] +Description=OpenSnitch is a GNU/Linux application firewall. +Documentation=https://github.com/gustavo-iniguez-goya/opensnitch/wiki +Wants=network.target +After=network.target + +[Service] +Type=simple +PermissionsStartOnly=true +ExecStartPre=/bin/mkdir -p /etc/opensnitchd/rules +ExecStart=/usr/bin/opensnitchd -rules-path /etc/opensnitchd/rules +Restart=always +RestartSec=30 + +[Install] +WantedBy=multi-user.target diff --git a/debian/python3-opensnitch-ui.postinst b/debian/python3-opensnitch-ui.postinst new file mode 100755 index 0000000..0b7ab1e --- /dev/null +++ b/debian/python3-opensnitch-ui.postinst @@ -0,0 +1,18 @@ +#!/bin/sh + +set -e + +autostart_by_default() +{ + if [ -f /etc/xdg/autostart -a ! -f /etc/xdg/autostart/opensnitch_ui.desktop ]; then + ln -s /usr/share/applications/opensnitch_ui.desktop /etc/xdg/autostart/ + fi +} + +autostart_by_default + +if command -v gtk-update-icon-cache >/dev/null && test -f /usr/share/icons/hicolor/index.theme ; then + gtk-update-icon-cache --quiet /usr/share/icons/hicolor/ +fi + +#DEBHELPER# diff --git a/debian/python3-opensnitch-ui.postrm b/debian/python3-opensnitch-ui.postrm new file mode 100755 index 0000000..8189482 --- /dev/null +++ b/debian/python3-opensnitch-ui.postrm @@ -0,0 +1,15 @@ +#!/bin/sh +set -e + +case "$1" in + purge) + if [ -f /etc/xdg/autostart/opensnitch_ui.desktop ];then + rm -f /etc/xdg/autostart/opensnitch_ui.desktop + fi + ;; + remove) + pkill -15 opensnitch-ui || true + ;; +esac + +#DEBHELPER# diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..72f3a4d --- /dev/null +++ b/debian/rules @@ -0,0 +1,42 @@ +#!/usr/bin/make -f +export DH_VERBOSE = 1 +export DESTDIR := $(shell pwd)/debian/opensnitch +export UIDESTDIR := $(shell pwd)/debian/python3-opensnitch-ui + +override_dh_installsystemd: + dh_installsystemd --restart-after-upgrade + +override_dh_auto_build: + $(MAKE) protocol +# Workaround for Go build problem when building in _build + mkdir -p _build/src/github.com/evilsocket/opensnitch/daemon/ui/protocol/ + cp daemon/ui/protocol/* _build/src/github.com/evilsocket/opensnitch/daemon/ui/protocol/ + dh_auto_build + cd ui && python3 setup.py build --force + +override_dh_auto_install: +# daemon + mkdir -p $(DESTDIR)/usr/bin + cp _build/bin/daemon $(DESTDIR)/usr/bin/opensnitchd +# GUI + make -C ui/i18n + cp -r ui/i18n/locales/ ui/opensnitch/i18n/ + pyrcc5 -o ui/opensnitch/resources_rc.py ui/opensnitch/res/resources.qrc + sed -i 's/^import ui_pb2/from . import ui_pb2/' ui/opensnitch/ui_pb2* + cd ui && python3 setup.py install --force --root=$(UIDESTDIR) --no-compile -O0 --install-layout=deb + +# daemon + dh_auto_install + +%: + dh $@ --builddirectory=_build --buildsystem=golang --with=golang,python3 + +override_dh_auto_clean: + dh_auto_clean + $(MAKE) clean + $(RM) ui/opensnitch/resources_rc.py + $(RM) -r ui/opensnitch/i18n/ + $(RM) ui/i18n/locales/*/*.qm + cd ui && python3 setup.py clean -a + $(RM) -r ui/opensnitch_ui.egg-info/ + find ui -name \*.pyc -exec rm {} \; diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/debian/source/options b/debian/source/options new file mode 100644 index 0000000..bcc4bbb --- /dev/null +++ b/debian/source/options @@ -0,0 +1 @@ +extend-diff-ignore="\.egg-info$" \ No newline at end of file diff --git a/debian/tests/control b/debian/tests/control new file mode 100644 index 0000000..2ae9569 --- /dev/null +++ b/debian/tests/control @@ -0,0 +1,2 @@ +Tests: test-resources.sh +Depends: opensnitch diff --git a/debian/tests/test-resources.sh b/debian/tests/test-resources.sh new file mode 100755 index 0000000..560d7c5 --- /dev/null +++ b/debian/tests/test-resources.sh @@ -0,0 +1,13 @@ +#!/bin/sh +set -e + +ophome="/etc/opensnitchd" + +ls -dl $ophome 1>/dev/null +echo "installed OK: $ophome" +ls -l $ophome/system-fw.json 1>/dev/null +echo "installed OK: $ophome/system-fw.json" +ls -l $ophome/default-config.json 1>/dev/null +echo "installed OK: $ophome/default-config.json" +ls -dl $ophome/rules 1>/dev/null +echo "installed OK: $ophome/rules/" diff --git a/debian/upstream/metadata b/debian/upstream/metadata new file mode 100644 index 0000000..556a1cf --- /dev/null +++ b/debian/upstream/metadata @@ -0,0 +1,9 @@ +--- +Name: opensnitch +Bug-Database: https://github.com/evilsocket/opensnitch/issues +Bug-Submit: https://github.com/evilsocket/opensnitch/issues/new +Contact: Gustavo Iñiguez Goia <gooffy1@gmail.com> +Documentation: https://github.com/evilsocket/opensnitch/wiki +CPE: cpe:/a:evilsocket:opensnitch +Repository: https://github.com/evilsocket/opensnitch.git +Repository-Browse: https://github.com/evilsocket/opensnitch diff --git a/debian/watch b/debian/watch new file mode 100644 index 0000000..383dd73 --- /dev/null +++ b/debian/watch @@ -0,0 +1,4 @@ +version=4 +opts=filenamemangle=s/.+\/v?(\d\S*)\.tar\.gz/opensnitch-\$1\.tar\.gz/,\ +uversionmangle=s/(\d)[_\.\-\+]?(RC|rc|pre|dev|beta|alpha)[.]?(\d*)$/\$1~\$2\$3/ \ + https://github.com/evilsocket/opensnitch/tags .*/v?(\d\S*)\.tar\.gz diff --git a/ebpf_prog/Makefile b/ebpf_prog/Makefile new file mode 100644 index 0000000..934951c --- /dev/null +++ b/ebpf_prog/Makefile @@ -0,0 +1,159 @@ +#taken from /samples/bpf/Makefile and removed all targets + +# SPDX-License-Identifier: GPL-2.0 + +BPF_SAMPLES_PATH ?= $(abspath $(srctree)/$(src)) +TOOLS_PATH := $(BPF_SAMPLES_PATH)/../../tools + +# Libbpf dependencies +LIBBPF = $(TOOLS_PATH)/lib/bpf/libbpf.a + +CGROUP_HELPERS := ../../tools/testing/selftests/bpf/cgroup_helpers.o +TRACE_HELPERS := ../../tools/testing/selftests/bpf/trace_helpers.o + +always-y += opensnitch.o + +ifeq ($(ARCH), arm) +# Strip all except -D__LINUX_ARM_ARCH__ option needed to handle linux +# headers when arm instruction set identification is requested. +ARM_ARCH_SELECTOR := $(filter -D__LINUX_ARM_ARCH__%, $(KBUILD_CFLAGS)) +BPF_EXTRA_CFLAGS := $(ARM_ARCH_SELECTOR) +TPROGS_CFLAGS += $(ARM_ARCH_SELECTOR) +endif + +TPROGS_CFLAGS += -Wall -O2 +TPROGS_CFLAGS += -Wmissing-prototypes +TPROGS_CFLAGS += -Wstrict-prototypes + +TPROGS_CFLAGS += -I$(objtree)/usr/include +TPROGS_CFLAGS += -I$(srctree)/tools/testing/selftests/bpf/ +TPROGS_CFLAGS += -I$(srctree)/tools/lib/ +TPROGS_CFLAGS += -I$(srctree)/tools/include +TPROGS_CFLAGS += -I$(srctree)/tools/perf +TPROGS_CFLAGS += -DHAVE_ATTR_TEST=0 + +ifdef SYSROOT +TPROGS_CFLAGS += --sysroot=$(SYSROOT) +TPROGS_LDFLAGS := -L$(SYSROOT)/usr/lib +endif + +TPROGCFLAGS_bpf_load.o += -Wno-unused-variable + +TPROGS_LDLIBS += $(LIBBPF) -lelf -lz +TPROGLDLIBS_tracex4 += -lrt +TPROGLDLIBS_trace_output += -lrt +TPROGLDLIBS_map_perf_test += -lrt +TPROGLDLIBS_test_overhead += -lrt +TPROGLDLIBS_xdpsock += -pthread + +# Allows pointing LLC/CLANG to a LLVM backend with bpf support, redefine on cmdline: +# make M=samples/bpf/ LLC=~/git/llvm/build/bin/llc CLANG=~/git/llvm/build/bin/clang +LLC ?= llc +CLANG ?= clang +LLVM_OBJCOPY ?= llvm-objcopy +BTF_PAHOLE ?= pahole + +# Detect that we're cross compiling and use the cross compiler +ifdef CROSS_COMPILE +CLANG_ARCH_ARGS = --target=$(notdir $(CROSS_COMPILE:%-=%)) +endif + +# Don't evaluate probes and warnings if we need to run make recursively +ifneq ($(src),) +HDR_PROBE := $(shell printf "\#include <linux/types.h>\n struct list_head { int a; }; int main() { return 0; }" | \ + $(CC) $(TPROGS_CFLAGS) $(TPROGS_LDFLAGS) -x c - \ + -o /dev/null 2>/dev/null && echo okay) + +ifeq ($(HDR_PROBE),) +$(warning WARNING: Detected possible issues with include path.) +$(warning WARNING: Please install kernel headers locally (make headers_install).) +endif + +BTF_LLC_PROBE := $(shell $(LLC) -march=bpf -mattr=help 2>&1 | grep dwarfris) +BTF_PAHOLE_PROBE := $(shell $(BTF_PAHOLE) --help 2>&1 | grep BTF) +BTF_OBJCOPY_PROBE := $(shell $(LLVM_OBJCOPY) --help 2>&1 | grep -i 'usage.*llvm') +BTF_LLVM_PROBE := $(shell echo "int main() { return 0; }" | \ + $(CLANG) -target bpf -O2 -g -c -x c - -o ./llvm_btf_verify.o; \ + readelf -S ./llvm_btf_verify.o | grep BTF; \ + /bin/rm -f ./llvm_btf_verify.o) + +BPF_EXTRA_CFLAGS += -fno-stack-protector +ifneq ($(BTF_LLVM_PROBE),) + BPF_EXTRA_CFLAGS += -g +else +ifneq ($(and $(BTF_LLC_PROBE),$(BTF_PAHOLE_PROBE),$(BTF_OBJCOPY_PROBE)),) + BPF_EXTRA_CFLAGS += -g + LLC_FLAGS += -mattr=dwarfris + DWARF2BTF = y +endif +endif +endif + +# Trick to allow make to be run from this directory +all: + $(MAKE) -C ../../ M=$(CURDIR) BPF_SAMPLES_PATH=$(CURDIR) + +clean: + $(MAKE) -C ../../ M=$(CURDIR) clean + @find $(CURDIR) -type f -name '*~' -delete + +$(LIBBPF): FORCE +# Fix up variables inherited from Kbuild that tools/ build system won't like + $(MAKE) -C $(dir $@) RM='rm -rf' EXTRA_CFLAGS="$(TPROGS_CFLAGS)" \ + LDFLAGS=$(TPROGS_LDFLAGS) srctree=$(BPF_SAMPLES_PATH)/../../ O= + +$(obj)/syscall_nrs.h: $(obj)/syscall_nrs.s FORCE + $(call filechk,offsets,__SYSCALL_NRS_H__) + +targets += syscall_nrs.s +clean-files += syscall_nrs.h + +FORCE: + + +# Verify LLVM compiler tools are available and bpf target is supported by llc +.PHONY: verify_cmds verify_target_bpf $(CLANG) $(LLC) + +verify_cmds: $(CLANG) $(LLC) + @for TOOL in $^ ; do \ + if ! (which -- "$${TOOL}" > /dev/null 2>&1); then \ + echo "*** ERROR: Cannot find LLVM tool $${TOOL}" ;\ + exit 1; \ + else true; fi; \ + done + +verify_target_bpf: verify_cmds + @if ! (${LLC} -march=bpf -mattr=help > /dev/null 2>&1); then \ + echo "*** ERROR: LLVM (${LLC}) does not support 'bpf' target" ;\ + echo " NOTICE: LLVM version >= 3.7.1 required" ;\ + exit 2; \ + else true; fi + +$(BPF_SAMPLES_PATH)/*.c: verify_target_bpf $(LIBBPF) +$(src)/*.c: verify_target_bpf $(LIBBPF) + +$(obj)/tracex5_kern.o: $(obj)/syscall_nrs.h +$(obj)/hbm_out_kern.o: $(src)/hbm.h $(src)/hbm_kern.h +$(obj)/hbm.o: $(src)/hbm.h +$(obj)/hbm_edt_kern.o: $(src)/hbm.h $(src)/hbm_kern.h + +-include $(BPF_SAMPLES_PATH)/Makefile.target + +# asm/sysreg.h - inline assembly used by it is incompatible with llvm. +# But, there is no easy way to fix it, so just exclude it since it is +# useless for BPF samples. +$(obj)/%.o: $(src)/%.c + @echo " CLANG-bpf " $@ + $(Q)$(CLANG) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) $(BPF_EXTRA_CFLAGS) \ + -I$(obj) -I$(srctree)/tools/testing/selftests/bpf/ \ + -I$(srctree)/tools/lib/ \ + -D__KERNEL__ -D__BPF_TRACING__ -Wno-unused-value -Wno-pointer-sign \ + -D__TARGET_ARCH_$(SRCARCH) -Wno-compare-distinct-pointer-types \ + -Wno-gnu-variable-sized-type-not-at-end \ + -Wno-address-of-packed-member -Wno-tautological-compare \ + -Wno-unknown-warning-option $(CLANG_ARCH_ARGS) \ + -I$(srctree)/samples/bpf/ -include asm_goto_workaround.h \ + -O2 -emit-llvm -c $< -o -| $(LLC) -march=bpf $(LLC_FLAGS) -filetype=obj -o $@ +ifeq ($(DWARF2BTF),y) + $(BTF_PAHOLE) -J $@ +endif diff --git a/ebpf_prog/README b/ebpf_prog/README new file mode 100644 index 0000000..ae33e00 --- /dev/null +++ b/ebpf_prog/README @@ -0,0 +1,29 @@ +opensnitch.c is an eBPF program. Compilation requires getting kernel source. + +sudo apt install clang llvm libelf-dev libzip-dev flex bison libssl-dev bc rsync python3 +cd opensnitch +wget https://github.com/torvalds/linux/archive/v5.8.tar.gz +tar -xf v5.8.tar.gz +patch linux-5.8/tools/lib/bpf/bpf_helpers.h < ebpf_prog/file.patch +cp ebpf_prog/opensnitch.c ebpf_prog/Makefile linux-5.8/samples/bpf +cd linux-5.8 && yes "" | make oldconfig && make prepare && make headers_install # (1 min) +cd samples/bpf && make +objdump -h opensnitch.o #you should see many section, number 1 should be called kprobe/tcp_v4_connect +llvm-strip -g opensnitch.o #remove debug info +sudo cp opensnitch.o /etc/opensnitchd/ +cd ../../../daemon + +--opensnitchd expects to find opensnitch.o in /etc/opensnitchd/ +--start opensnitchd with: + +opensnitchd -rules-path /etc/opensnitchd/rules -process-monitor-method ebpf + +The kernel where you intend to run it must have some options activated: + +$ grep BPF /boot/config-$(uname -r) +CONFIG_CGROUP_BPF=y +CONFIG_BPF=y +CONFIG_BPF_SYSCALL=y +CONFIG_BPF_EVENTS=y +CONFIG_KPROBES=y +CONFIG_KPROBE_EVENTS=y diff --git a/ebpf_prog/arm-clang-asm-fix.patch b/ebpf_prog/arm-clang-asm-fix.patch new file mode 100644 index 0000000..d8dd394 --- /dev/null +++ b/ebpf_prog/arm-clang-asm-fix.patch @@ -0,0 +1,14 @@ +--- ../../arch/arm/include/asm/unified.h 2021-04-20 10:47:54.075834124 +0000 ++++ ../../arch/arm/include/asm/unified-clang-fix.h 2021-04-20 10:47:38.943811970 +0000 +@@ -11,7 +11,10 @@ + #if defined(__ASSEMBLY__) + .syntax unified + #else +-__asm__(".syntax unified"); ++//__asm__(".syntax unified"); ++#ifndef __clang__ ++ __asm__(".syntax unified"); ++#endif + #endif + + #ifdef CONFIG_CPU_V7M diff --git a/ebpf_prog/file.patch b/ebpf_prog/file.patch new file mode 100644 index 0000000..a9c3668 --- /dev/null +++ b/ebpf_prog/file.patch @@ -0,0 +1,11 @@ +--- linux-5.8/tools/lib/bpf/bpf_helpers.h 2020-08-03 00:21:45.000000000 +0300 ++++ linux-5.8/tools/lib/bpf/bpf_helpersnew.h 2021-02-23 18:45:21.789624834 +0300 +@@ -54,7 +54,7 @@ + * Helper structure used by eBPF C program + * to describe BPF map attributes to libbpf loader + */ +-struct bpf_map_def { ++struct bpf_map_defold { + unsigned int type; + unsigned int key_size; + unsigned int value_size; diff --git a/ebpf_prog/opensnitch.c b/ebpf_prog/opensnitch.c new file mode 100644 index 0000000..916740a --- /dev/null +++ b/ebpf_prog/opensnitch.c @@ -0,0 +1,508 @@ +#define KBUILD_MODNAME "dummy" + +//uncomment if building on x86_32 +//#define OPENSNITCH_x86_32 + +#include <linux/sched.h> +#include <linux/ptrace.h> +#include <linux/version.h> +#include <uapi/linux/bpf.h> +#include <uapi/linux/tcp.h> +#include <bpf/bpf_helpers.h> +#include <bpf/bpf_tracing.h> +#include <net/sock.h> +#include <net/udp_tunnel.h> +#include <net/inet_sock.h> + +#define MAPSIZE 12000 + +//-------------------------------map definitions +// which github.com/iovisor/gobpf/elf expects +#define BUF_SIZE_MAP_NS 256 + +typedef struct bpf_map_def { + unsigned int type; + unsigned int key_size; + unsigned int value_size; + unsigned int max_entries; + unsigned int map_flags; + unsigned int pinning; + char namespace[BUF_SIZE_MAP_NS]; +} bpf_map_def; + +enum bpf_pin_type { + PIN_NONE = 0, + PIN_OBJECT_NS, + PIN_GLOBAL_NS, + PIN_CUSTOM_NS, +}; +//----------------------------------- + +// even though we only need 32 bits of pid, on x86_32 ebpf verifier complained when pid type was set to u32 +typedef u64 pid_size_t; +typedef u64 uid_size_t; + +struct tcp_key_t { + u16 sport; + u32 daddr; + u16 dport; + u32 saddr; +}__attribute__((packed)); + +struct tcp_value_t{ + pid_size_t pid; + uid_size_t uid; + u64 counter; +}__attribute__((packed)); + +// not using unsigned __int128 because it is not supported on x86_32 +struct ipV6 { + u64 part1; + u64 part2; +}__attribute__((packed)); + +struct tcpv6_key_t { + u16 sport; + struct ipV6 daddr; + u16 dport; + struct ipV6 saddr; +}__attribute__((packed)); + +struct tcpv6_value_t{ + pid_size_t pid; + uid_size_t uid; + u64 counter; +}__attribute__((packed));; + +struct udp_key_t { + u16 sport; + u32 daddr; + u16 dport; + u32 saddr; +} __attribute__((packed)); + +struct udp_value_t{ + pid_size_t pid; + uid_size_t uid; + u64 counter; +}__attribute__((packed)); + +struct udpv6_key_t { + u16 sport; + struct ipV6 daddr; + u16 dport; + struct ipV6 saddr; +}__attribute__((packed)); + +struct udpv6_value_t{ + pid_size_t pid; + uid_size_t uid; + u64 counter; +}__attribute__((packed)); + + +// on x86_32 "struct sock" is arranged differently from x86_64 (at least on Debian kernels). +// We hardcode offsets of IP addresses. +struct sock_on_x86_32_t { + u8 data_we_dont_care_about[40]; + struct ipV6 daddr; + struct ipV6 saddr; +}; + + +// Add +1,+2,+3 etc. to map size helps to easier distinguish maps in bpftool's output +struct bpf_map_def SEC("maps/tcpMap") tcpMap = { + .type = BPF_MAP_TYPE_HASH, + .key_size = sizeof(struct tcp_key_t), + .value_size = sizeof(struct tcp_value_t), + .max_entries = MAPSIZE+1, +}; +struct bpf_map_def SEC("maps/tcpv6Map") tcpv6Map = { + .type = BPF_MAP_TYPE_HASH, + .key_size = sizeof(struct tcpv6_key_t), + .value_size = sizeof(struct tcpv6_value_t), + .max_entries = MAPSIZE+2, +}; +struct bpf_map_def SEC("maps/udpMap") udpMap = { + .type = BPF_MAP_TYPE_HASH, + .key_size = sizeof(struct udp_key_t), + .value_size = sizeof(struct udp_value_t), + .max_entries = MAPSIZE+3, +}; +struct bpf_map_def SEC("maps/udpv6Map") udpv6Map = { + .type = BPF_MAP_TYPE_HASH, + .key_size = sizeof(struct udpv6_key_t), + .value_size = sizeof(struct udpv6_value_t), + .max_entries = MAPSIZE+4, +}; + +// for TCP the IP-tuple can be copied from "struct sock" only upon return from tcp_connect(). +// We stash the socket here to look it up upon return. +struct bpf_map_def SEC("maps/tcpsock") tcpsock = { + .type = BPF_MAP_TYPE_HASH, + .key_size = sizeof(u64), + .value_size = sizeof(u64),// using u64 instead of sizeof(struct sock *) + // to avoid pointer size related quirks on x86_32 + .max_entries = 100, +}; +struct bpf_map_def SEC("maps/tcpv6sock") tcpv6sock = { + .type = BPF_MAP_TYPE_HASH, + .key_size = sizeof(u64), + .value_size = sizeof(u64), + .max_entries = 100, +}; + +// //counts how many connections we've processed. Starts at 0. +struct bpf_map_def SEC("maps/tcpcounter") tcpcounter = { + .type = BPF_MAP_TYPE_ARRAY, + .key_size = sizeof(u32), + .value_size = sizeof(u64), + .max_entries = 1, +}; +struct bpf_map_def SEC("maps/tcpv6counter") tcpv6counter = { + .type = BPF_MAP_TYPE_ARRAY, + .key_size = sizeof(u32), + .value_size = sizeof(u64), + .max_entries = 1, +}; +struct bpf_map_def SEC("maps/udpcounter") udpcounter = { + .type = BPF_MAP_TYPE_ARRAY, + .key_size = sizeof(u32), + .value_size = sizeof(u64), + .max_entries = 1, +}; +struct bpf_map_def SEC("maps/udpv6counter") udpv6counter = { + .type = BPF_MAP_TYPE_ARRAY, + .key_size = sizeof(u32), + .value_size = sizeof(u64), + .max_entries = 1, +}; +struct bpf_map_def SEC("maps/debugcounter") debugcounter = { + .type = BPF_MAP_TYPE_ARRAY, + .key_size = sizeof(u32), + .value_size = sizeof(u64), + .max_entries = 1, +}; + +// size 150 gave ebpf verifier errors for kernel 4.14, 100 is ok +// we can cast any struct into rawBytes_t to be able to access arbitrary bytes of the struct +struct rawBytes_t { + u8 bytes[100]; +}; + + +//used for debug purposes only +struct bpf_map_def SEC("maps/bytes") bytes = { + .type = BPF_MAP_TYPE_HASH, + .key_size = sizeof(u32), + .value_size = sizeof(u32), + .max_entries = 222, +}; + +//used for debug purposes only +struct bpf_map_def SEC("maps/debug") debug = { + .type = BPF_MAP_TYPE_HASH, + .key_size = sizeof(struct tcpv6_key_t), + .value_size = sizeof(struct rawBytes_t), + .max_entries = 555, +}; + + +// initializing variables with __builtin_memset() is required +// for compatibility with bpf on kernel 4.4 + +SEC("kprobe/tcp_v4_connect") +int kprobe__tcp_v4_connect(struct pt_regs *ctx) +{ + #ifdef OPENSNITCH_x86_32 + // On x86_32 platforms I couldn't get function arguments using PT_REGS_PARM1 + // that's why we are accessing registers directly + struct sock *sk = (struct sock *)((ctx)->ax); + #else + struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx); + #endif + + u64 skp = (u64)sk; + u64 pid_tgid = bpf_get_current_pid_tgid(); + bpf_map_update_elem(&tcpsock, &pid_tgid, &skp, BPF_ANY); + return 0; +}; + +SEC("kretprobe/tcp_v4_connect") +int kretprobe__tcp_v4_connect(struct pt_regs *ctx) +{ + u64 pid_tgid = bpf_get_current_pid_tgid(); + u64 *skp = bpf_map_lookup_elem(&tcpsock, &pid_tgid); + if (skp == NULL) {return 0;} + + struct sock *sk; + __builtin_memset(&sk, 0, sizeof(sk)); + sk = (struct sock *)*skp; + + struct tcp_key_t tcp_key; + __builtin_memset(&tcp_key, 0, sizeof(tcp_key)); + bpf_probe_read(&tcp_key.dport, sizeof(tcp_key.dport), &sk->__sk_common.skc_dport); + bpf_probe_read(&tcp_key.sport, sizeof(tcp_key.sport), &sk->__sk_common.skc_num); + bpf_probe_read(&tcp_key.daddr, sizeof(tcp_key.daddr), &sk->__sk_common.skc_daddr); + bpf_probe_read(&tcp_key.saddr, sizeof(tcp_key.saddr), &sk->__sk_common.skc_rcv_saddr); + + u32 zero_key = 0; + u64 *val = bpf_map_lookup_elem(&tcpcounter, &zero_key); + if (val == NULL){return 0;} + + struct tcp_value_t tcp_value; + __builtin_memset(&tcp_value, 0, sizeof(tcp_value)); + tcp_value.pid = pid_tgid >> 32; + tcp_value.uid = bpf_get_current_uid_gid() & 0xffffffff; + tcp_value.counter = *val; + bpf_map_update_elem(&tcpMap, &tcp_key, &tcp_value, BPF_ANY); + + u64 newval = *val + 1; + bpf_map_update_elem(&tcpcounter, &zero_key, &newval, BPF_ANY); + bpf_map_delete_elem(&tcpsock, &pid_tgid); + return 0; +}; + + +SEC("kprobe/tcp_v6_connect") +int kprobe__tcp_v6_connect(struct pt_regs *ctx) +{ + #ifdef OPENSNITCH_x86_32 + struct sock *sk = (struct sock *)((ctx)->ax); + #else + struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx); + #endif + + u64 skp = (u64)sk; + u64 pid_tgid = bpf_get_current_pid_tgid(); + bpf_map_update_elem(&tcpv6sock, &pid_tgid, &skp, BPF_ANY); + return 0; +}; + +SEC("kretprobe/tcp_v6_connect") +int kretprobe__tcp_v6_connect(struct pt_regs *ctx) +{ + u64 pid_tgid = bpf_get_current_pid_tgid(); + u64 *skp = bpf_map_lookup_elem(&tcpv6sock, &pid_tgid); + if (skp == NULL) {return 0;} + struct sock *sk; + __builtin_memset(&sk, 0, sizeof(sk)); + sk = (struct sock *)*skp; + + struct tcpv6_key_t tcpv6_key; + __builtin_memset(&tcpv6_key, 0, sizeof(tcpv6_key)); + bpf_probe_read(&tcpv6_key.dport, sizeof(tcpv6_key.dport), &sk->__sk_common.skc_dport); + bpf_probe_read(&tcpv6_key.sport, sizeof(tcpv6_key.sport), &sk->__sk_common.skc_num); + #ifdef OPENSNITCH_x86_32 + struct sock_on_x86_32_t sock; + __builtin_memset(&sock, 0, sizeof(sock)); + bpf_probe_read(&sock, sizeof(sock), *(&sk)); + tcpv6_key.daddr = sock.daddr; + tcpv6_key.saddr = sock.saddr; + #else + bpf_probe_read(&tcpv6_key.daddr, sizeof(tcpv6_key.daddr), &sk->__sk_common.skc_v6_daddr.in6_u.u6_addr32); + bpf_probe_read(&tcpv6_key.saddr, sizeof(tcpv6_key.saddr), &sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32); + #endif + + u32 zero_key = 0; + u64 *val = bpf_map_lookup_elem(&tcpv6counter, &zero_key); + if (val == NULL){return 0;} + + struct tcpv6_value_t tcpv6_value; + __builtin_memset(&tcpv6_value, 0, sizeof(tcpv6_value)); + tcpv6_value.pid = pid_tgid >> 32; + tcpv6_value.uid = bpf_get_current_uid_gid() & 0xffffffff; + tcpv6_value.counter = *val; + bpf_map_update_elem(&tcpv6Map, &tcpv6_key, &tcpv6_value, BPF_ANY); + + u64 newval = *val + 1; + bpf_map_update_elem(&tcpv6counter, &zero_key, &newval, BPF_ANY); + bpf_map_delete_elem(&tcpv6sock, &pid_tgid); + return 0; +}; + + +SEC("kprobe/udp_sendmsg") +int kprobe__udp_sendmsg(struct pt_regs *ctx) +{ + #ifdef OPENSNITCH_x86_32 + struct sock *sk = (struct sock *)((ctx)->ax); + struct msghdr *msg = (struct msghdr *)((ctx)->dx); + #else + struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx); + struct msghdr *msg = (struct msghdr *)PT_REGS_PARM2(ctx); + #endif + + u64 msg_name; //pointer + __builtin_memset(&msg_name, 0, sizeof(msg_name)); + bpf_probe_read(&msg_name, sizeof(msg_name), &msg->msg_name); + struct sockaddr_in * usin = (struct sockaddr_in *)msg_name; + + struct udp_key_t udp_key; + __builtin_memset(&udp_key, 0, sizeof(udp_key)); + bpf_probe_read(&udp_key.dport, sizeof(udp_key.dport), &usin->sin_port); + if (udp_key.dport != 0){ //likely + bpf_probe_read(&udp_key.daddr, sizeof(udp_key.daddr), &usin->sin_addr.s_addr); + } + else { + //very rarely dport can be found in skc_dport + bpf_probe_read(&udp_key.dport, sizeof(udp_key.dport), &sk->__sk_common.skc_dport); + bpf_probe_read(&udp_key.daddr, sizeof(udp_key.daddr), &sk->__sk_common.skc_daddr); + } + bpf_probe_read(&udp_key.sport, sizeof(udp_key.sport), &sk->__sk_common.skc_num); + bpf_probe_read(&udp_key.saddr, sizeof(udp_key.saddr), &sk->__sk_common.skc_rcv_saddr); + + u32 zero_key = 0; + __builtin_memset(&zero_key, 0, sizeof(zero_key)); + u64 *counterVal = bpf_map_lookup_elem(&udpcounter, &zero_key); + if (counterVal == NULL){return 0;} + struct udp_value_t *lookedupValue = bpf_map_lookup_elem(&udpMap, &udp_key); + u64 pid = bpf_get_current_pid_tgid() >> 32; + if ( lookedupValue == NULL || lookedupValue->pid != pid) { + struct udp_value_t udp_value; + __builtin_memset(&udp_value, 0, sizeof(udp_value)); + udp_value.pid = pid; + udp_value.uid = bpf_get_current_uid_gid() & 0xffffffff; + udp_value.counter = *counterVal; + bpf_map_update_elem(&udpMap, &udp_key, &udp_value, BPF_ANY); + + u64 newval = *counterVal + 1; + bpf_map_update_elem(&udpcounter, &zero_key, &newval, BPF_ANY); + } + //else nothing to do + return 0; + +}; + + +SEC("kprobe/udpv6_sendmsg") +int kprobe__udpv6_sendmsg(struct pt_regs *ctx) +{ + #ifdef OPENSNITCH_x86_32 + struct sock *sk = (struct sock *)((ctx)->ax); + struct msghdr *msg = (struct msghdr *)((ctx)->dx); + #else + struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx); + struct msghdr *msg = (struct msghdr *)PT_REGS_PARM2(ctx); + #endif + + u64 msg_name; //a pointer + __builtin_memset(&msg_name, 0, sizeof(msg_name)); + bpf_probe_read(&msg_name, sizeof(msg_name), &msg->msg_name); + + struct udpv6_key_t udpv6_key; + __builtin_memset(&udpv6_key, 0, sizeof(udpv6_key)); + bpf_probe_read(&udpv6_key.dport, sizeof(udpv6_key.dport), &sk->__sk_common.skc_dport); + if (udpv6_key.dport != 0){ //likely + bpf_probe_read(&udpv6_key.daddr, sizeof(udpv6_key.daddr), &sk->__sk_common.skc_v6_daddr.in6_u.u6_addr32); + } + else { + struct sockaddr_in6 * sin6 = (struct sockaddr_in6 *)msg_name; + bpf_probe_read(&udpv6_key.dport, sizeof(udpv6_key.dport), &sin6->sin6_port); + bpf_probe_read(&udpv6_key.daddr, sizeof(udpv6_key.daddr), &sin6->sin6_addr.in6_u.u6_addr32); + } + + bpf_probe_read(&udpv6_key.sport, sizeof(udpv6_key.sport), &sk->__sk_common.skc_num); + bpf_probe_read(&udpv6_key.saddr, sizeof(udpv6_key.saddr), &sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32); + + + #ifdef OPENSNITCH_x86_32 + struct sock_on_x86_32_t sock; + __builtin_memset(&sock, 0, sizeof(sock)); + bpf_probe_read(&sock, sizeof(sock), *(&sk)); + udpv6_key.daddr = sock.daddr; + udpv6_key.saddr = sock.saddr; + #endif + + u32 zero_key = 0; + u64 *counterVal = bpf_map_lookup_elem(&udpv6counter, &zero_key); + if (counterVal == NULL){return 0;} + struct udpv6_value_t *lookedupValue = bpf_map_lookup_elem(&udpv6Map, &udpv6_key); + u64 pid = bpf_get_current_pid_tgid() >> 32; + if ( lookedupValue == NULL || lookedupValue->pid != pid) { + struct udpv6_value_t udpv6_value; + __builtin_memset(&udpv6_value, 0, sizeof(udpv6_value)); + udpv6_value.pid = pid; + udpv6_value.uid = bpf_get_current_uid_gid() & 0xffffffff; + udpv6_value.counter = *counterVal; + bpf_map_update_elem(&udpv6Map, &udpv6_key, &udpv6_value, BPF_ANY); + u64 newval = *counterVal + 1; + bpf_map_update_elem(&udpv6counter, &zero_key, &newval, BPF_ANY); + } + //else nothing to do + return 0; + +}; + +SEC("kprobe/iptunnel_xmit") +int kprobe__iptunnel_xmit(struct pt_regs *ctx) +{ + #ifdef OPENSNITCH_x86_32 + // TODO + return 0; + #else + struct sk_buff *skb = (struct sk_buff *)PT_REGS_PARM3(ctx); + u32 src = (u32)PT_REGS_PARM4(ctx); + u32 dst = (u32)PT_REGS_PARM5(ctx); + #endif + + u16 sport = 0; + unsigned char *head; + u16 pkt_hdr; + __builtin_memset(&head, 0, sizeof(head)); + __builtin_memset(&pkt_hdr, 0, sizeof(pkt_hdr)); + bpf_probe_read(&head, sizeof(head), &skb->head); + bpf_probe_read(&pkt_hdr, sizeof(pkt_hdr), &skb->transport_header); + struct udphdr *udph; + __builtin_memset(&udph, 0, sizeof(udph)); + + udph = (struct udphdr *)(head + pkt_hdr); + bpf_probe_read(&sport, sizeof(sport), &udph->source); + sport = (sport >> 8) | ((sport << 8) & 0xff00); + + struct udp_key_t udp_key; + struct udp_value_t udp_value; + u32 zero_key = 0; + __builtin_memset(&udp_key, 0, sizeof(udp_key)); + __builtin_memset(&udp_value, 0, sizeof(udp_value)); + + bpf_probe_read(&udp_key.sport, sizeof(udp_key.sport), &sport); + bpf_probe_read(&udp_key.dport, sizeof(udp_key.dport), &udph->dest); + bpf_probe_read(&udp_key.saddr, sizeof(udp_key.saddr), &src); + bpf_probe_read(&udp_key.daddr, sizeof(udp_key.daddr), &dst); + + u64 *counterVal = bpf_map_lookup_elem(&udpcounter, &zero_key); + if (counterVal == NULL){return 0;} + + struct udp_value_t *lookedupValue = bpf_map_lookup_elem(&udpMap, &udp_key); + u64 pid = bpf_get_current_pid_tgid() >> 32; + if ( lookedupValue == NULL || lookedupValue->pid != pid) { + udp_value.pid = pid; + udp_value.uid = bpf_get_current_uid_gid() & 0xffffffff; + udp_value.counter = *counterVal; + bpf_map_update_elem(&udpMap, &udp_key, &udp_value, BPF_ANY); + u64 newval = *counterVal + 1; + bpf_map_update_elem(&udpcounter, &zero_key, &newval, BPF_ANY); + } + + return 0; + +}; + +// debug only: increment key's value by 1 in map "bytes" +void increment(u32 key){ + u32 *lookedupValue = bpf_map_lookup_elem(&bytes, &key); + if (lookedupValue == NULL){ + u32 zero = 0; + bpf_map_update_elem(&bytes, &key, &zero, BPF_ANY); + } + else { + u32 newval = *lookedupValue + 1; + bpf_map_update_elem(&bytes, &key, &newval, BPF_ANY); + } +} + +char _license[] SEC("license") = "GPL"; +// this number will be interpreted by the elf loader +// to set the current running kernel version +u32 _version SEC("version") = 0xFFFFFFFE; diff --git a/proto/.gitignore b/proto/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/proto/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/proto/Makefile b/proto/Makefile new file mode 100644 index 0000000..bbbe2c6 --- /dev/null +++ b/proto/Makefile @@ -0,0 +1,14 @@ +all: ../daemon/ui/protocol/ui.pb.go ../ui/opensnitch/ui_pb2.py + +../daemon/ui/protocol/ui.pb.go: ui.proto + protoc -I. ui.proto --go_out=../daemon/ui/protocol/ --go-grpc_out=../daemon/ui/protocol/ --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative + +../ui/opensnitch/ui_pb2.py: ui.proto + python3 -m grpc_tools.protoc -I. --python_out=../ui/opensnitch/ --grpc_python_out=../ui/opensnitch/ ui.proto + +clean: + @rm -rf ../daemon/ui/protocol/ui.pb.go + @rm -rf ../daemon/ui/protocol/ui_grpc.pb.go + @rm -rf ../ui/opensnitch/ui_pb2.py + @rm -rf ../ui/opensnitch/ui_pb2_grpc.py + diff --git a/proto/ui.proto b/proto/ui.proto new file mode 100644 index 0000000..d28ff6d --- /dev/null +++ b/proto/ui.proto @@ -0,0 +1,129 @@ +syntax = "proto3"; + +package protocol; + +option go_package = "github.com/evilsocket/opensnitch/daemon/ui/protocol"; + +service UI { + rpc Ping(PingRequest) returns (PingReply) {} + rpc AskRule (Connection) returns (Rule) {} + rpc Subscribe (ClientConfig) returns (ClientConfig) {} + rpc Notifications (stream NotificationReply) returns (stream Notification) {} +} + +message Event { + string time = 1; + Connection connection = 2; + Rule rule = 3; + int64 unixnano = 4; +} + +message Statistics { + string daemon_version = 1; + uint64 rules = 2; + uint64 uptime = 3; + uint64 dns_responses = 4; + uint64 connections = 5; + uint64 ignored = 6; + uint64 accepted = 7; + uint64 dropped = 8; + uint64 rule_hits = 9; + uint64 rule_misses = 10; + map<string, uint64> by_proto = 11; + map<string, uint64> by_address = 12; + map<string, uint64> by_host = 13; + map<string, uint64> by_port = 14; + map<string, uint64> by_uid = 15; + map<string, uint64> by_executable = 16; + repeated Event events = 17; +} + +message PingRequest { + uint64 id = 1; + Statistics stats = 2; +} + +message PingReply { + uint64 id = 1; +} + +message Connection { + string protocol = 1; + string src_ip = 2; + uint32 src_port = 3; + string dst_ip = 4; + string dst_host = 5; + uint32 dst_port = 6; + uint32 user_id = 7; + uint32 process_id = 8; + string process_path = 9; + string process_cwd = 10; + repeated string process_args = 11; + map<string, string> process_env = 12; +} + +message Operator { + string type = 1; + string operand = 2; + string data = 3; + bool sensitive = 4; +} + +message Rule { + string name = 1; + bool enabled = 2; + bool precedence = 3; + string action = 4; + string duration = 5; + Operator operator = 6; +} + +enum Action { + NONE = 0; + LOAD_FIREWALL = 1; + UNLOAD_FIREWALL = 2; + CHANGE_CONFIG = 3; + ENABLE_RULE = 4; + DISABLE_RULE = 5; + DELETE_RULE = 6; + CHANGE_RULE = 7; + LOG_LEVEL = 8; + STOP = 9; + MONITOR_PROCESS = 10; + STOP_MONITOR_PROCESS = 11; +} + +// client configuration sent on Subscribe() +message ClientConfig { + uint64 id = 1; + string name = 2; + string version = 3; + bool isFirewallRunning = 4; + // daemon configuration as json string + string config = 5; + uint32 logLevel = 6; + repeated Rule rules = 7; +} + +// notification sent to the clients (daemons) +message Notification { + uint64 id = 1; + string clientName = 2; + string serverName = 3; + // CHANGE_CONFIG: 2, data: {"default_timeout": 1, ...} + Action type = 4; + string data = 5; + repeated Rule rules = 6; +} + +// notification reply sent to the server (GUI) +message NotificationReply { + uint64 id = 1; + NotificationReplyCode code = 2; + string data = 3; +} + +enum NotificationReplyCode { + OK = 0; + ERROR = 1; +} diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..d282411 --- /dev/null +++ b/release.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# nothing to see here, just a utility i use to create new releases ^_^ + +CURRENT_VERSION=$(cat daemon/core/version.go | grep Version | cut -d '"' -f 2) +TO_UPDATE=( + daemon/core/version.go + ui/version.py +) + +echo -n "Current version is $CURRENT_VERSION, select new version: " +read NEW_VERSION +echo "Creating version $NEW_VERSION ...\n" + +for file in "${TO_UPDATE[@]}" +do + echo "Patching $file ..." + sed -i "s/$CURRENT_VERSION/$NEW_VERSION/g" $file + git add $file +done + +git commit -m "Releasing v$NEW_VERSION" +git push + +git tag -a v$NEW_VERSION -m "Release v$NEW_VERSION" +git push origin v$NEW_VERSION + +echo +echo "All done, v$NEW_VERSION released ^_^" diff --git a/screenshots/opensnitch-ui-general-tab-deny.png b/screenshots/opensnitch-ui-general-tab-deny.png new file mode 100644 index 0000000000000000000000000000000000000000..809fc8efa2768a65825a57f981fd91323b1ee238 GIT binary patch literal 109267 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 C=3yZK literal 0 HcmV?d00001 diff --git a/screenshots/opensnitch-ui-proc-details.png b/screenshots/opensnitch-ui-proc-details.png new file mode 100644 index 0000000000000000000000000000000000000000..1190a29e7cd99033370a72e3c6b625362af04ca6 GIT binary patch literal 76358 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 @ezn6#X92`1|nD5^Y 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ì)$i)m796qD(y_x%VMU6%G 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 G5}E+-hx?`g literal 0 HcmV?d00001 diff --git a/screenshots/screenshot.png b/screenshots/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..5e8c5ab761ea6465a3a1f8ae0851dcf1d400c051 GIT binary patch literal 453311 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�?Y81A!$!lB@w{kovGeiI>2df)=XU5e=i5F1 z+e35q)a%-(dUh|2Ob(-`Q=V10O@0&Wmto8=4hNQ7dEV!zd->=em|koTWAA8mO&%7O zf%G2t700Z$eG2l+{itdfgv-tDQ-LT&5oWsA^VB1d9UIY9wM%v`g6`v#m&vro`|XO5 zoPr`Y(YI0cO5DET75P%(LjN8o0ckUCqC8E*iUT*$EG(0SuZjqw;a>-xbJkw&hZI%( zVLpLLmC%7ED`_)YZ0IA~G5(`gFdDs0lGI-gxd}HfQBjXNU4MoNH%cr!-R9cTk-7>1 zFa7CvNfrv*rFo^_-r^wI%`<@)o~hs7-Ou$kci?u#E#e&Qm+-H-*|~SY$TmzXjet3f zHRiw=hk_gC?!>D7pe~po8+D9p%h<2OlETj>aDfFGhnEtFy?A2<GqH+z0He6Cp!gBO z;2AI#7Sd`Hdn&47g-kM&fXDr@-h91>+N3K-tffp-V^fhzDTF~@ZOWfE<^K%Vi+@8) zm8Aa@CU7gX4w}|>B5*4KE4oz*3@}+^mo?W42AsDvaa^Va_wq9*%8m*VuQtiXv+whw zBVwWl{hTflE<I?gIt#3f`Gz)WhXlHE)lq*y#iyR1voik?ChQf?1D!mNC(P0-?B0>D z|13-f5dNvi!^xjcB&IeH&m%Y|el*Jz7_-ADnevO_Y^0LzfB}kjtnfKTHZ2w(Hv$It z*yjlYjy8bZHkY3h8jQ>N%8VcH9`B1TvQAci1-B*u0n*_W{lyL^(24v|&RdwM{&0*R z>v2}?%v){5)n?jKDu1p95(!Y37yv|QiISF;@D2i%qgkU_pL*{)Y`OonML7J(ka2Jr zY(kr5ll=rV4nq^3`m_cUO(7WoO?9338Uml21bV`T<q;X3Xvqj7$At0?L<FS9^dgJL z;Nh>{_f`U-;SkR4CU-~>&|w{4Iy}{V$r5OR)iebFSOs1Xi-Mau_C9Isq7ouB7%7nl z8?C=!E=LL~#=!uu$o^AIDmS-@vKmNDJV7~iI(|=*7q$sEkzRScLYd=`z{smyDZR2y zcr=U&)qM5p4L@{r^vI*<h>gPD+&V7}{nTiwR%5X$w*7gzp8DPMEtOrr#nA{6z>*dk zZ{*RwE7s6V1=3qkx}&@}+04E=xn<t3)@ycq>)5>aF&iz#sK!jB5p4Kz^=}{?*XQaL z?rjXKR@P&N)C@9IG_P8y2=v~@MO~*O<L~?(_m|2ybsAj%yZ2!8Dt}ZAHO}f=W|$Ze zi1T7_mh{Z&BiUkczS8{xdFOpZ$jxr3>W*LFftk69$ZcV_OjnH|CM(|B+iIk#Al^*x zX=|I+@ba=(I0KuA_s+DX?%o&WqWwIF(<gKK3N!ZNacjq;NRai+H^kfiUk6Ur7O`XC zg{h5y-TC}G&)*8B>V{Rq)f#sltHr{T<fejT@0qX1G#-&l9or{^f9s-~?}!$wj}cnb z!-KhsKi>F^9#J077W+4v3dMH?OrzTGY%qxGj*}5?iTSVUio4A}{Wx=2IC?waXLrbd zn_sP0{zOBH6;L<g^SC-LD%57$oQ651_i;_n_h>$?uCX|CJ#)0)zirD+w0d%1SkYR` zdJ!r-N<Rupl!gKP$m54Pa)f_uYO_+DtuE^M+`rh#Z@8U8CqyC!^}8V}#c?9K--iE2 z-OzkP+krkRt0Ux_d-lU)cyhvc3R}hGxynUKW9!zRaTm+7F-!N`;cLiq(36SZO*3cb zmXf&Q3o7_y<%{;r<q`(YRN)~;(scqW#WsRiMQ@Wn@8OUy$*kA0wfza^x%wYkY^;Xh zM>5^W3p%NyM~@qZ=M357ePp_3wqIk{@1aAkn(C6(gks;{By4v}Qx(aArVibndv`@) zDj3zf+Fz|kJYAo9Od*e#eAX9Sw6ZoeveIrUDto?L-ovti2u_~;@whs5x3`*`RqG9A z)7co$ul^(5Pcv@^0_fix55f*)+g~*vCLVWZJtY2i?wvm*O8i({fUFR6Ae@|fT`l5* zp_pD{ZLWnkNWNCfiC*V>i-39g=qFuC-IrL3cn}Uqy}1<q8SV7oqa?@z;a1vaV@)=g z)X8X*c_VYZ#_+xZ?URqwdi27?^Gj0^#>UgnDJ4nN!?KS$jdP=i`)}t{BMAV2YyuS- zg^NX|NI=N^b<fv3(NMBj&<t`arnK?4Ti#SArI3#LKV<2k7~gE9J#Q1?|FHl}q5k-R zQrZk7gO&_<WR+dWdEb+8{u9q?#0p~2*pg{g-GSJBs>9J52xx(@zQ!Q*4-FNX8vX8) zn^GUe>0xq<uB4qcw{Yb66GN<)V%luKljGmkPzyLjnU3^RF~bj)ip0}jGTPKMcjGxu z8I0rG{D3nX8|Ox+uKXmC5(1MB#32q$Z*qaa$jHbzh@I)ve)uFV{#@H-pV>i%gWg42 z+Wo!r<er+E092@1uu(f37~G}rO)K4|EV@`f41}SOQJ$bHELD|8#Eg^}8y)?3#a%dO zmtn+4=P*6?!yCzK{`b^8QyB=Su8PCh{B_>yd5zBTOXLlCc_wR}ooLRs&yzRYO&}0< z>OxAeCOPg0uumu21ctWYn~wRF=Uc7;ZKr4tA`zZ~7ZUBhQ{Zy!@t2&{Q7Tse6HxIp zH~1%>x@KNpC+D}R8ut616+bVJhcnIjdKah4lR5}v(~b@!Ov`DtDqpA^(<rW;WUQHV z(iR8=%*?GZ!hTn$C5kd)!(VjTnRca)zd9H*w9lYIVY=ZxuD-3AN~Ex#bxfi0Qnp<E z6`>?usEVK-|E!YyxzV_sFYyv~E?n9X<a6~o%48x)_!qfYfs|^EEU0vVFlIICJXuT% zvj@ku%(fLrnS_uhDBvsHfH%smKrsb6rk`Vu$T}R>IO*%ZK!(N4_v;fb62)yHQhY&o z@a&?#O{~EMB=D`6LIQ(Mu<CSGL;rCoG+u$21is$9JG?r5<!p92yVz8`8#7-bl7Q2E zx5v8Hz%aPKt~$x%zrIzWuBMPkd~5FPuJ`-k=xY35WIqotg{(APJ|0O-x`jQ^@p;VW zN|Y!<784!_ILpR@sbr|Oq`f;`oji9ByXJJc9rg#`<ONErp>oqitGkbBUCuXgFJ@Xd z-~Nq1#O*}CDE&NWh9A+sxm}-azHpui6TR_X`;Decf&|#xRtbzNFaB`n5pcWiB-MRg z8bgRY#7l@Tb1gJoPeR*uK1oX#sU3x#GJttFR&Tz1QwvvrAZYjLUYD3>_V$^_)u+Bs zyA*}48Mx>A<+=1ypgVb)tkY?4QPAcBw<{EC+@AR+WW%J(_p#7b@9jQngX{JqqHQY< z1{dNQ?!dljDs&$44GBP|hVXhCyS!QDePOciV3PH4Ngz)PH{@I=w0U*D{%L)}wUm`& z#CaxLUx@r86qFyAFp3IB6!w_E#j@zuC5%A+Rj!{_>-s>q+>+wI0J6Uvz+ISdzr8xp zOH<?7yY>14U@=E;hj_wQXukJ_-95;+I=Tns1cU4u<E#bRL*VtUAd%&HJJg$9WLwKs z%;Tq$E9FHd=Hq2wp1O(Eo1rKmk*%EObD`e7EvZoizfpOvno`3Tn7oa;t8KWCo;Bqk z(#+fJ?s$c`f|{-G+)vfwNoRmOkALZmFXVmRI*+O;qXkYb?a9_XoTG`amWmvmQy^>= zi4j#<`Od0(rS<v%#Suh2S-Lupz-Uttbh?0zyPh^#bA1m@tQVST`+y-`47(>`FSCF^ zXr-PLE_Ea_s(Js6{`~wk8x`!C-^WymK3!*~;?bP7-d%t3_)xr{QO$Q@X1GGgK)7<; z-R0pqL}Jj;R+Rj`*}Sv++pmSn4Xo0tDzM>cSiHx$$5~<W#`m_5*m!ilpBL2|?r!_# zH_s$KuPl%`GvxPawm4n**sBoOYXWn%BNx5Q{T+Db9IKv-H%xW>C`HGKm>cTNV}`#Y zQ_8>f2|!;TV)Er{)k&Uj4NhJ{(d<!EQ<E$;g<RB3t1lpJj-}K9NPiaJ6Z8%3cVKJQ zk2Mz5qxDL5jRd8))t==lirv?0vH`H<m(K#oxO$7s&ff9^o<0ccN}31K>qOjCE@Dt5 z(2Fkaj{90#PETZ$(ff)%$3(}KCEiC|WIrjf3+J$*RZ3+&WcZM=qW_@%Trop{`zH2D z3>c8I_GzRE+ZT_^dac1sD3#ysnsf2>UqWdyv2BkZXp9ZfNtY{AzF3)V2%<xe6wIco zt~Bz=DyfxFUupyjGmhn8qXpo>=VBX6KtExC+pvvv7JvPXHdXZI@VBEeTy3fx6y8sL zs2{fwt)wv8YamVe87qD_N+N@Z6w=Se)fIRJm5M1c{15f-ozCi2r$<Y5O+307b>VTg z^7&x_rC8)QCGIi|-EDF;8$^ESCoDWyMMRLyhRg@ToAzoGgfYyLE=2rkwdrkqCAOL7 zt)|;PL~*4`KfAILC1tb?p1jGMru`*wzh_~!`V}>RRl@K7aHW5|Wn{s~t)q`ccIXQA zCsqzDZJvIfxcCLGS`_T(oYzy<k7ThVDVBZ^Joygq41%BE4>E25%wVj;E-D^Ok-Y)9 z_gT%D(TU`Cnu%!JcPu(V7#o%!NE8GlgQXHH%A`%B$7<9nbLZmA>qC>iDitD(@ykoF zK(~)YWs}MOsGxQV;>*}%QiSyf3lG13qJ>3_TIrVme(1Zjh@o>QHqhwvK0pHEiytwv zOoqYBmlv>j7<hQQJ`z7s_p*KX;^P#>_poF=@FH0jf)V+}`|65^a78(COwa8&^9zh5 z+E}<LO{W64$-gF{&D)+Kiv{odVY%okzQYVU7_-B}VuE@Vn{E$->Xk9UwfkGvHsKB4 zt-rJ7ChJe-8cEjQ-=D`_y5{%h90nzQ7tl%lZ?Zk?dl!uCSMyBX*%tA?x=&SYX*T_O zr&*OGZn2S<A#3XP>W@}<c{3S7&)#-EX;Ql}S-al(CZx}Dyy(+m$qi?k{n4%>-)5#B zKr86zd6UsSk<o1VwvRS;@P0J9Iez4Hx+}mp_;KAl)F_~kI4Mkl{clx$t{5-H0LGc4 zVs$Xb{qoT(?qaiktGw~-ZmiqwzL#@4Um^+>W~%IsdZ5dFYj)lcAu1whh-l?zHRQl& z-r-UZ#v9D>d+B=RHriZ)*DV6$KEF1!-TX>pT=>tp_VL1k)n=Xj1Sxaddt{YKmX9gr zhj5qKb_P{?`fP#u-0;RPZJBZaS*JqhaYEAz^PAy*LA?()48UXDg!IeiN?NV^#n+B@ zQ!6vvUuQjOZOhE#dTPzvC(Ig+!9&?RRPWc-RnKF^<xQ5GZPp|HIB4zmVDdKw!H=m* z3|;&6@bCqQH?cuKy4k47W*4{NvY?8>A%#yBiZ(({KXZ%w%wn_7l}PmLB#{Ml=mO4- z5QN7)A9#+&*EwovFsiZmfB=X#<F62MKaKgLcH`WAE>*Hg!0k+qnOUpZ$z%O^AY8rq z3^{y|#3ZAAHIJ`&{c2%GZne2;b8hQ&myU4-xOp|-A$~#_pZD|f(7V=1CS1eq_KD{A zL3R6kVf9yQo#z#~MZr|T^A5vupy)L1+?OYN2MIvnJ>SFMG_LB@dm?!r8xf+OwVSzF z_=(P)I!QsDTDO;`$T7hecN7G~XrYS=y^A&1=>Sqs?dD2rE8W_ad;%Q(bkFtM>`dGD z>}0p^td6eH^VI@R_hm_rV`-WS9d&sp!?9{&UrI<HxobE{yIy-WEWX<4-RxPoY<%tX zjz&UA*m2R0rlMD3Wpg2Ib~$boT6@@TPH(o{Xc0Bf=G7$5^BQ@%VAQHNJ2O$x^;!(! zRFPjk*rapFZ~$*PQ=i%1)#o)`tqJ-z(qwWOjP+~0@2}&eFEG2mml1xbd#|{C$^#58 zmD1d~KLqRhc=sTE8f_!;{wH{2)3v?cMU&0JpL0sv<l{~AlTgt6CV!9cE0f0ySGQ-| z(bR0zc@*BtlVQ6=s8pRe#vVvok7iV=WTxprUKbE-hV}12L}h+a7|e~0B_hR60f1C% zLv}`0P-cpWWODDD3k%d3X%Vb$#r$2XO=l}n=M*_L##>VKLMNswP8B74$D0z!o1aPC zV89}#+YfE+jUvo^R)v?7J&K{w93(CJSDL%NOxa&Pzvskf6!3H8O<d7ues2m*d;JPT zj0}aH=8%gCU2!t*b4H3G8Up!Vx$<L%hsZKmLLd{q#JC6cL0R*ZqUFLo`?VH3NmB3I z7CQu(9H6PD>ym*aP=w(4Bt1R7kcFYQiE%704t2o47$rlH1tko`@B_KKlrdO2V4pim zRsjG=G7c00?fs<C@I#GA&Vco828yua_h|Ux-&PxS*6qD{cQeY+#U(WAx55R9fF@d1 z)o33`xBNX&>!jEoLQmlQgH@H`OD9kwQiw>2Nsm!@db*ih{epB*_s!U3kpQ;93<qjh z!!;*OV{wh$rMamN>v7)wI$;?2ERR7=jknj)>3m5QbE}O8V~CDlS$Vdt$Uzh}x)xv8 zS9pwAn9$yln``zK9O*>Z{PpWs6nGE?EGjCc7yyn^OXOW-J#qtAt#8Un_bE|2bPDoG z2opVWuoI(-z9$2x{yzHg%{LS0T6+N;<ll8z79Y%onPN=j&NDUFZ+_L6kmnpyh(GlA z3ArHzF3Z8(+~W+dO1@4Q)+`M%nMTe(UPfgs@VPa;$GeSb3AX)G<tHzNq^`Lj=7=(T z+>eZC+hns?{++{Z(_x381iaD!U!0-Mm%jOReLf4v=HF?e)>;DZxd%R&ETZwp>?Cg| z;*U$au@q4qLS73F(fU(1$=>^YFK^F5FZY_RPu`;UU8Wl)?fS3dAXdQ4!K;vf2epUe zb<XIhpQKPs#`A4<@!e+Pq8meU<!^L?yh6=?l@_1>rZ>A9mgOrsY_vQ6iGi7HGZDTU zVD@>RaPkm`0#kB%K3#RdS!jt}<?irxxnD)i4xyD3H%!uDa1h-UFJ7cgDV@WFqH!Os zynj*n+i}|yG9mQyd__;UO#*1T2%~V3oqBa2u75rCo{w>%{cu}3rp016d^SX+(Rf}v zo84x<QoQ<*WqXIiX#fDsJfv)Mni08u-$Vre%jxY;S-og6m>1f@>*ad08hq`g$<E+h zCd`*_e_h%YE#BZzxLGDk6n!A*dXDn4D1SNg9;G;gAi(>XA!660*p(ly`@j2xoteuI zxsDWe{a>mDhhYJsk=?g_+dq5EOwKHz0GnSwdd(0p4z(7HFB7=Tz3nE&^Gj<Fs9f}b znRYXwE3uM#PuKKGDXtGEKK!oZnei?si|rpIL%N&}drW(1G+vWl`;poqKHlqpayvaR zgwCjz44VZ%>p%CQFg#?td)n{_ifbVNDtO=P*xh40CwQF@;Q(FRotqh5E^^{|FDWv( zXHG-J#TxOKe=II|yYAk<K|n~m8LRrQHzX>g9%o6Q8y~x+P{B@w3$DE+7p}9ot_eJ+ zJDF-7t*aGr1k{Ri_UD=CwlnrRlVS-;#)X*X>qLDbp&|`8x9T^WRrkN7*1Xhyx)4h1 z&g1^O;~6Lbfz<V>W80<Uek!^gNanpHo{awfSeMP;s0NtAJkV?K(eC%XVP4~c1^hU3 z>t~qE%5cAp@PfZR=R2+m&wiBT1BeZ+zYo@{oC#%jnOI${2-s+{8eQaOK9*wZr+0V{ zyhiVPb<X=u2{(6KwzFpIHz`Edk$C96PEW1sJ&p%TSqs1Q42iEEKYX~3Y`5Q46T2Pm z*{JEf{)0fOBHQg1Ls*F=+_7o(q^^&D?n9zU*Q+PD^556kepy<f@@W%$zHHj0@;h%` zQdsNz9Og4`*7?ws{`fdjbzb}8!|lGR8**DIm9VJSTC!P_hIhQ`YE9bHbs%sz>eRW> z_9NS`pL)GxUoyJme)Y-@05H5wHHnsvMN*+ogmUI{9~IU!KV2?HLpzlpc$mBpA+LWu zGDAQkZh3I?IeEUp{cu#bfd+oipuylJV2RwRWc!ft9fl%>9<nJstIAYs#5&=J4*dLk zqYOcUQ$n}g{<6>-@)IGmMvv@N1BB@kg<g(~kO7oEF}-lgiDVziY0z*i{1b?*I+O92 z5?M8Bcg(chkI?p8vTvo?(N4QMMn-%B+~=dUb%$1?F>0=a&Ys=g#d#Bn#JrY=1;ye# zP-wfMo`KA_z@PZ(Utsr(6HvbTwI1q!kSuR{0t<_x>&Y*K#f~1&TC0FyI*{fbHq`Np zmP*_=69Ad|=Lem|2F3{<2OS3NI#hN7WQ_#1g$q-j=af&Ad=Gu!@xPh;3BcI+c--+> z=`a|N!3>ZaLzHBU_K4b%I{GKeGT8DXE&w3IaWWDe$_A5{fj#L^04_5~z~st0peyep zkp@pCTwrQWLIXm@$ia$C$ba2WVSLGG6VNmq;y;T3(frE8$pVEPY9VS;zbDa<(zj2n z=^7$koIithr4vAeY-D{bsH;tWSnK9rE@+;X5fc9#Yr#{Ahn55|dX3u;hJN<1f-eKk z9&5)bkC#9pT#jwo0Rw>r!N5MlK6yUQPV>_SmG%6^^L!V`{yN{|u7n(xADUg?{UlTK zBIUD{nMIU@Xr$a-Q)bjqK8b@LaJ31oUra1qYqfbXq*BVl!opay@N8N8t98|(x{}Il zv%vf>P5T&uqPp(=7f-k!H3oRf3de0#L?uRS-%5o`mlqa3hg0)U8m>hS?JOV1)r}D; z?ZW~ld>yP!cl1>Y^Q1=c#I<g<>9U^*LjlmC{)*r@&H)q<9vcYO)J&cG@mdxUQ`38n z|M-GY!tMM=Udo5Y#(Ge?QSW8hr9&)3af%1cHUOYwTiUvl!LINeF-#TK^(!3(P>Kx{ zc^hg4+TJNJ@ofM3#OG4s_3`!qA=4lDF1O^*>886w1>aoHeYS=VTK-vFyi8lqMnC=B z;1umzO|>b;_^m1v{$q9HZGJ9$QUBrTR^|N-|DaQxD@wreN{vBJs5zji^F9fi*FwML z-!2~miPqy#*d~SGUU%d>`(7&qO7^>bgIjgGpn03<HCYhgJwm`qf0DoLw#C18dWa-_ zyZ$C4)KXz1sQ3EPZgWS<!h&_icm!h-fl_OEG!yoT2EJR=b8j!Xy6YVmo{G#jpVtx! znoL+!1UJlt5_YEd8*gS3f{Pkort&Brq-iZoj2s)iv7rF~X8g>Kn%`MD&LNw)qWD&Q zS~yB}q<x=4a~)vA*Q^3n5*k8A{-&V<$iO97o@?f{ff5w2rM$`faV<iEd<C9vQVoYn zLjKbBybkc?a~P*Jr{C34{Kvk{p8re*OKUeuj}4r5(q%GiEwvf{6=63RgEdw+Q>x0F z>Zr>_6b1x?#CQ{tgZk%K5Od-umP#FYk38Q;{Lu)TIk_51T`yaxWZrANJiH)*kfl&r zL%<*ttmM|HGsKA?Gqz~<Xr+2rg!!f8C=#qlnNyHo6)H2cn6)`r^-N-_fjvQa)k(Fj zcMXr+eiEiK$>7`vM5fOIR5F?krAI)ZQ}TCUPM=f6@7yyP{7#Ggd*Ekaj4FNNh|z99 z?e_?Wb9<p)DA1og$e}s@;<RCtF)7c1G8EL)PsTvtQ}7y5^FSUdvYKj;Jq{kiM<%Qi zKpDahFe&;nj1?{1SC&O2KAbFemsxPdi_q^ZLqk+icr>E5B+F55GamiK#67FCN`UWj z3+gNf;R&I@5HXAEZjT>K2r7P^-OiwfzE@#x*M=Ptbb@b1R*0&*9_C!3$br5+tw4e7 z?4JtsxhAVIf3dudwzf74W3~09w)t`m_64lyA+&y=KMrEFqC;61@-7gmfdm-s+aBVs zpf2YN?T0+v7K|CIwuX50J_}Xq>{NPqxSyww!xT@X(OxBZd50iOoIRoE>~q>d0OP9~ z0%ePs^zub1tc&4_leznH&srsm=vMbkgwHF+=5=h2HF=^onAB_BAzJS3Ln(1Wmt&`} zP1KsZ$wSi0L*|3!sz*CH@xuGMTK`e^o1|hMWI+=9&>p$LBOf)yCnv)K<OJe9=ju<Y zGEZhk?TC?Mh9z(x1aE6&6Ax`iU`osNCz#aB@~*Ee{8H{`t7PmV&p=_~Hu=ke3g&>I z8mLDHN34^ObI=>ba&rxmMih+jKp^P5q{*HS=h3LYyelxRwb9{jNd74qpf;px-*sCN z5+ofjKS%41`<rCbVDX1<U>y(tr(vJ0cJ@i~vWeTQ6X_a%dS*lC90Hd)NCW0NYKZKt zrM1;JlNsGMN2mm(b2pE&b+DKS`Ce?VXh>im4y0Yv^<!N#Rn}c}{rP;o-GuGTmxQzK zJv&$!Edbc(tFn@d^(W-}6Fo^*<spdSsdTkKSdZMfYR`ryP)ergG?wGyr0Gp0ZcD!m z)t`*MoNidu^smOJVbz>#Z3gT(60p0!?A_OX7I3JMhX;KkG>8_Ibs~|cG{NHvF1g?W zH4>s;i;=U~mDRV}ed^_#|A0ckFASp75o@zV;G_tZLZT1x41k6a<M{HQ%7<f&Gydg^ zJt35@?NSb6s_d`vmB#e1R$$~>SME%CwqHg9XRWGAno09>OG|}y+LI&#^4#Z--i>cF zM;rkDm(zOdLA0Sh00{^j)0PziOMCS|m1Uf`TYR8Ok>8~9-|{31kAcr-^j%y$d2rBu zaOwBnf9<P<&+tmA0}i6Ob%dX*9-`EZw}(aF;S1Ht$+0K_u(0;<(5*m!L`?~QgjcEr zHKCdTyGnow2#$Oh2!KNBt+44{5V7`HqPiA<LB{6DK+(KVjPzB-9*$lvLjKBI8piNX zLp2Y}i;Fy<>bd2v%x55=;G)4Lt=LPy>mA?d++p#0aKf6!x!q3qCGvyGSsHTl8!6>U z%}~?a+sCheLfKMhdk(8iAGlw0vPTlf<q-|Y$Po-ulcuhr51XY$^GL(Mp@B@(QXKKM zZ!K{C&Vxk>5Ui?^CSGcqECGfU5yDfzaiZ}hLP5iGmKgh=6wa9Swi=}Q1I!i0;L}Di zjw=w-oMLkTIsPcqr5Kmpe!rVaTGU7ukDK~1ax_u+ka-mi4swW>`H(QsOK6an8Ac^~ z<d=|875ka$<ms+e<;_^Al00E<xPLvxDq#7z#~O9Y7rZq5rG{`p8&gv(mP?^#vqb}= zkV~hv3b>xun(A(D#?<k3sPP0G1mT4%D}?EU^lhs5ha1ThHl`(9BEe?Ll$1*9gDq!n zY#(qG*v7Pxf9_{Sx@%m#hrIA9h*T5&gptG{JsQ23zJ6o?@D$)5=1+hv0Gdo@0%?{5 z32U`ccM&b5AD$Cjq{{O416Fr^3qxf=xzd3=L~0QeC<2TD_TRngg!!XP-n49Cvsiuy zkm(E}lHvO2Z240}OOhwdsItv3&Cv$ZGBeNwHqfjZ1F*;m<}U5XaI;PbR6o-i>x>2D zyoMP8TTruU0$B!~=st;5$;3APp#a#XQZS)lVP0Eyzq>z>BKUTk;vqt}0>~8sJF)<B zDu2HdZg6k?Q}d=b3dtbE@Z+qDOL(kitG_k7OUPrQFDP6~i@;;?mRyy)Nsdk{$=HO= z-{B`-1}jKxu&LeojMwhpA1D}e-RPm8kVGGiYQ6Qsx;W+IwkR;5l`3zN<$&lWJg#Ip zfrM2#3(8m7oIpZTb9s21!s6u~fh+(4)zZvTv)fvi7JJl)Em&CWs|N`jGGf+`3{e#a zcJk3{f;kr2!UViPTPhYeGEH<&U!l`96~Oo;hP3eh9v~{vC|n=;FLJ9YmT8Kvb$5!D zxUFBwFGN_}K<AoGWS|Jy2qbx6_1%b}cG=7vuqPHnY@YkUH2nEa-H}t+<U&m=F^*k0 z?7uM%nQN(@FQ|Vi#?@;gPrxCfBf@{mfN@=(yItd&9?sGHiAAmgUlL<Em|~VHf;a(- z074`qSN{2(I+x=wJ}0U)+ScDD-H=vTpzc3jbYKH5my)gKhO8~NjxQPZZY(+!tVsmB zh_7gt8d`mbUN0OLV*@%4h6lXSTD3BDU1=ZAxw7b|o-^cBlt?m^_j!C>lEzZ_9pU=* zBlI3#=poM!=z%aX+@MR&1Dps!5tYSod52tQ@YPOwJHCqDekkqPE~OB;mPE&i=l(PK zb#;#Z_iu}gv>U)!MWYBmKq?@{VFFGRU=B1RK!7y|!Zjm;z>zAG+&2{t6BafKRR0o- zv+c}zQ;%lg&;q!#h>?R*E`6~SY&jGan{(Z?DY1WLfkGFTrec~@q+oN!f`>j){@oD) z<kI?5D0_XL!)Bp=-Obc7Vrw$MgJ<R03H@`IV)i{0083Yxfzn?CCKC=|?|n-l4JPvd zrTk!10g(T;ENL2gy_y@_X9Wf-4)Wl#8Tpct<7Ti1Q{t9X2cltGC6Dw)MA%I_(-kHe zU|j|T12K7Wz%j>yz{)RHMXAC)H8`L^GGK2n88u)O2cs*$UEiH0E0q0w50Xa`3Z~0V zaEG=`wwnm^gQc_20RwpQqFq}7lu~{GDmJC;Q#w|oj^Mx|XgdEnE)ZZ8Ln_vmss04S zS|VHm)N#!qLmWf|#uEe*WI){z0m(FVJMFESN9m((gOc?~c`U`W%e)4TIj`!jy&GW` zDsk1;+f`s%h83lGOak-%)|v;2jwCIZa;&V(EUc_BQ5a}xt>Q9%?}4f?9X3WsfP|+Z zngT(v@nl3w5~DflX{x+GTe2*^7wKq1(1lES_7Hs2?-C{2#?3~&hQ;f0gvaFSVy^!F z;9fdp2BXHNrjR^4z8&b8Yq93<1wY@O@4U>P8yXuqI5=+4*T<Ty08l!AcCMNhAifiv zsJ*Rbcq5K+5~x-8NtS&ua4BIiNMgqG+F<_#Xc%E6e}J@+m6pZ>HUN+l3J<}6dhnB% zNE|2;h4=Dvy5i1$QGErz$F#>e5neY_<9ii#DO08c^fd?T8(mw!g))AMQ$Ze1&4VXH z5Tg`17B2V&O8-r1<C8SG>!16-zlvTd>|OhtFBq&~i%nanc?`rvB7nwBAccB1=!pqZ zAWwJE$`k|@i(O*mgwn#g=;8_}z_YXTLILPldjJ5D7>Pbeozi2cgJf1>=IAZm{}X*6 zrz_`wEI=uA;HasZswAN8N}AmZfpM2t8dXR2EoiNl?Xkl9^I|nZ1Hf&au82`lLpAtG zercAJnJ9Gv=_Y^SjtT5XQJVy2lA;tUOl1}itU*T(hvQ&7mNt;2984m4*Y~^{7>rDB zcRX5IS}J+HFP!m18RTZ!W~Ekku4^p}1EwJbq4w9Dp&mpi0>B{GrVWuRH;q~P>l;kp z&mR}<fsZkS950miTUgm-ShJLXB55N$MU5w)pIJ*%_&aHIDx?k~b%WkL{10gJD8nO^ z4=Lu#h#-wf8<Zs6Rsgh)Z7-brFx*25f}X7`rF$LwS8`=(xmloyE6FDj+d-ZkWrqYD z!zih`s8wl?0VRTX&8+y22E4Jw<8nX))sDOOv$7a0Bv4}4AH^UT6{uso6?KpcmIekI zNI`4{ImX2_c@XP<odwGgx{!f%Jyg}z9V@G<tj$*pOxWhI#g5B#f<v9DDI7!x|6Mt* zXtsGiW$-w--ySAf+I+aackFF?YOlA`a7HXFz%xK1Liu?#$pF3p^K}g5M1wRD#8UCZ zQX<PCz3yNAeUrGsrre*ynYC{BUM{*xBPD)@nbKmDV-wTt7+D5P8F{Qsa8KLz)ps(j zm%*dst*t<^-8YR0)Kv`G^AIIP0ZSVj6NLy#moi!shDy<9dho(XApp!h^FQ@{@lK?l zG#{B1k3H)Aqe4@-^p)e+M$lDrD7He~s+6TIuRkLK9<8APh>Y5&;JT#oVZ;c#T;p1` zzX!&sBW;9-D}KH{!c9~$@}D7E|NmBkkIX>d<HOHjEAJ{!(j4gz6EkRWYyh;cxrDtq z&k0o?pqJYO>i~>?VovI-ER7bxzV8lcx=b=w65(SD3n-%Kc{R-7eTB=zx+T2DAO;=x zs2ukYI40nT7{i5FW^}#=><*oXK)E7zbeOgu|Ay5<4lH+!cB5I3(yvpYH<H!(W?R`1 z_{^U3hwgVX3aFO1!leM^&@+@$5%LA7q0eTEC)ZjX*i!%cLN#U`fG|V-9TZr%xaE1{ z*-+yhBoMdPC}6*g{mbyk%J0@kBz?j@_Ein+CedTMnGX>)=zed5=TCT~+Y8bUW>N6m zCDp8V>zZIF&INB}l*6LMag}C)VPVDmdu|$ZpM7OStG4`EfBmYg^E10Puxh%^RQ+7y zrW8G#juaP>nH#8;6eJaREx(9ObnTtQ$rb>VYPr4dtzd)#u)eB#W;pc1=0&WMjUraV zN_-~14a`X(2(Fd{Q2RmyfB<kGl5&CUBQLgeXoE<45oS~{YCImt+(sHxIjxPg_2*&6 zF*elv#!4OnG<<6AC<<6EITc%Zx^W}6A_J$3>CW2PyPfC*o8`Lvrl#s0qhM70nKhlw zg%tt?O3O(&;1&%;2LWpSsespn`)oIXiDVc-9=qyqfER1aojO{f+nwbG4y|>v$TYyi z!NCb#f(R)JV%!HAwTiTBN#>$ay7cYncq~8q{(Lju3xkOK#%!r_>0GW=a)`kqAWgxq z*y!wou(;~9fAMc#6W+;LqL1q{C_n|2P-Mt|9S}$5D%a3}^*=>?AECIPJI0Fe41;5F zJ#&$yCM*DTAecOGNJ&SYJ@aH3qp!NA=rgGu6OgKax$Ev*t;$O=YS37us56@J9mtJO zZyGlVQl0$2;rYe|Bo9UZdm}C~O6C27Ec8-TOWZHA;gA)j9_Q2Cwu0A5tqz)VDuUh~ zC+$S7vo1WrW42QS6fK#H;Q0FHRW{UIBpgzksbtU_hq&)XJ77UsO8KC^L|Wv7J!fT# z5c!AG+iKp<0w$aU9ZQ&dG`1`HWznh<%r%*?t8XFTW`ISuMnCk>-T7jpGwxe^Z7pm3 z$eh*kTA>WCNa%@iMGWeN3lgZv7}$pdN>G#r`pO)!l|YdZonv3eSTS*1zso|E1qP85 zX6Mz@U*nKU0aKX!>z0bk1Y-z41H~EI${!TLS&8I@bc)Q=#3ueKN9`eG!sMT(anPBK zh(E<VE*N(A@sA`n-l&|9Ou_ss{tYDKpWMlS$bwt2NT5GXx~1Jff~OWuvTGe<xolCn z2G0S%z>f&J>d)N;{-jf4*VqWb7#sqNv8iNqoZJY>tN?gG1}i0`D*fxU?sT$nd#cnA zX&BcA8nk}}{P`57IvOrU5$GOeF}9s3PH`3y@2s4JL&D~2`<C<!(Gb+toW3wCP-0yf zl_jSAu_HqF2ZGNf^$!<r`fz%AXkdjA%@w6fD|%CD8QA3rA*Po@cF*@`79P$jh5-hF zJY2I!9C<Z=W6ZSXSO6^8$i()SkG-P7OHBgauaC0SZ~N&syw;;PM4HvT4!UD?M!B-H z9J5vBzgj~BrH(Mk0xzmCgyX%liogr$lmH^IAc}~4`0m#wOP|8L${Nilt+-AaS8=;? z8{&vP4#ejwk-9c^F)_*na{`3%Sz&EOw>D%>PCxPLi<v%!shao|rvDAH|CEOiq(;$n z*rntA+?dL~jV@-6`1{lh_TqPPq&pa2d`z`ix7@<Lcp9_xWvt*Q%xiz#kyQ=sS985; zHy86o-jILd!XAK-lTGJ5)2Z=k@l|B9OR~kMmCy4HS*Sv`3Lu(IQ8~a99Sk-lgDR|} z2$te-s#=k1H!`5NhY<S~jUyKZ<8A!9T#B-f9}`)eh|7?xP$W0TEUn4{1_z*nIEa^@ zU<?;#-wRc;$+sIC=y2Y~WKMprzyaLz!}<{h>=O{;3dBC`o1T(Ht)qiC0tbrpJ-Z@< zLW98xf-)l=mbh><p<UJ@MI}HHTLGe87ulHVyMlL-1;M#ZeKM?L<b$<nu}iNYU%UjA zkGu?Jzz;D*U|E7;7sS~?mHI=6p{qUsF#M}nj1pD=-#IW)8TN+=;${CvsjN|`Zx0mE z7dYXEh)wdqFOSG78vuk3&45f6pdY>!X-E+c=u$|z31Z~2-t&z~_}`j%V8t0psA2Jt zCmS(cjot7O)3F?hsqymR4XPy3Ziz6X2dq}jmh|-Xra1{BSDMz7%nf^Xx6+GJrI*D9 zp8~4&;XrkIAYmV>r*7jE{AeVQ?5v~Z>V#3=c8QthYLj(kNeKsPc`Kyz$+9`q@ehh$ zHU1SMsOm&<wt?6Ka(KBdMfo{AP~;aac3#5QuN;8u>)&j!vGT@7l?44Yun(sVJ4+Q6 z9gI5#N~;na8?Fn)qaT|?V`(}gM#=97r~Xi+OD`pQ@DaOK&Vh(o{m0}sg+>-<<Y=#Y zfJ1Eoc)^l^!sdLslviUahZVps5A0rn?k?RO7*NI^E&|vU5CP(o8#x*Me?yN7Sy4~q zed{V5CG1#rt?`vn&i|Nl2rpi^)jg`m7rCd7vGa49r?%wLoA5*MxQp93yT9;+bHS!2 z_HTxn=z`~7)jRo%QgSDSZ4}|zIM_7xYWfbLOW}2DWT}Ii{OuLGqm3QE*xrhK58qCJ z%rP2NPsLcolqVW=0BOo-l>4!pH5){8m!LI5hA2OAC19!>m1Ww!0+w_42)773Pz$7% zVsiEE?D0i-#E1y69E;(^u2+I&x02turxoEtl;YUXKTdvi*lgnZ+qX`6@H0b|B>>uD zFP|g3&v<H?38-VMyKzd!h%sbjWWwf(=88|nL|`arB&_3N)du3B$%5*tG0*TnK4Bj# zr{!~KfmkA81_Z%oKxsg4gRENs5m72_uVI!^kBvZa3uUgYR2@inj}GEl5!`RE=mf!( z^z^&e?=loak!|6)OC2pdWK`(q&$*k{Y;_hPYv&vbCZvr5JtU89R3KIgNFUx|3$7?H zx3IE0QlAeZkbrp3a1bD%DalOFEXx1zD`~uFV<HdkGk^$e2AIZsdsbFf5Iv$yfF$~X zRM7&Dk|1)_Jt@Os$c}OB8qdPgQm6hG+5s?JVt+iH%jJA+Kk4(T-G8lj<cZRE=j+8D z59e72g|lYh29YF7blz8loIn<a-{uZUltJqB#`eUZ(7+N)0_k1eWm06gYKl};&+q}M zDvGQx++`fZjOjt5rrm6<K}2&WW|g=qqPqgx|99{H-|Bd~Ded<O^Or7yeOQr_pB0oC zN!*R@+uf>>tsk#&*{)SykG`%zc{=&Lux+s?f_%pJo@jSB$;soM@0n~kFexVfC|NH! zs`*brQ@7O6Ht3G6`RA}zhf}qO%UZ=3b`5IlP*!<vO%(w%TiCy-raZ8HEBy3I7LVa? zz{R&p$0d?U#=qE7_xB|xcdAb8Cm85=MjVx4Zh|9Dr~@hNqj|7j=UgIKK`92n*b9ny zLzqBXThR2-&<%enMGT<5hMD|?1w&Ifu7Cp<D$dY<`DIT$F&NBQX2g85nq8bQjlgs8 z%B%t-B2R^TsJfLmg+=_xF3;H+>_ekup-D}*n_<UfBrFNQ0)*+XJXJ7#6+skr=e128 zhJlK;5V-azqw0K+6onQUOuaN>4^D(6%0b~9VgvD_gZuv45fU`B4w{#}G&VQ;vgi(C z#-b?vs%v0W0T}#+6zlkWW@#W0Y~e3z!;t=^MhguQ#6lz>EG7ihfU7a(IAt5MBU<(X zg95?ZFYOe<zzkaaiE}B~X$PWs<wd8~10}J7ISy24DKuDMl5t8A9grYB9THYG%J5J5 zX^%|-07aHH#z5a@xmL3cG44VO5~=^y`7+M6>3FunNmL8zB=+#&BUg8(Qz$;YZ^8!P z1%qIVl+S)iRb$7zY?-V9bChjp-N^b5H1#MC7d>t{q0@W>4>G`t4aft^-$a!vg}29J zvhfNpO}?2H3IM=t<eqn=QWu~v8AJMTWQeg(*054O2$ZnkVYO;5P~eZ_w@vZ8=c6Hq z?*4VE=YwgV{oxN2d$qg|Px^22VebkvA56XPn*(RvY2SzRj=fI-`_RJmA+AVd^^3v* z5>Vh0LM64kKRtdk5Vt}R2E$=AQLP!-+0)RYz8&UW3sU4>5^S<sz+uqqP1X5Uj^K|j z2C->&)?JDX0L%&$mqHj|;OA$siZ=HBvxh0{@3^0$wZ*U0*h%|Zk<Gr&U;*)K24X4l zWoZPzPd*?;1T=-udI*5U#+|FW`+G`5MuJGF%i!SPduP&4Xh5NA+aU$iURXs@oCA{> z)3feH<EFJYvgjMb)KA)ii_Y69rP$Z!4hTpCG45Mb3qyi-5A4A{+5Bw@&q;;qt`2Rs zv&XOT0v=Dhym`fkFRUw$!O@uZ8?01Z+7&;7rNpco#hT>7NX@N4(ipDZ{qcN9%qbac z$y`VFWtDFkH&sg6Ow_*afVE1l{aG5F&zxTBLt!g|6_z}3i){SPpEapAes6E@C?Qj} z(Jd=px)C3O(ENcF-oBEhriyP$D;nWxwn|r~h#zUGA`BB_!{^JCPo@%yj2xdf?I<nH zEh?hM3WHGdV|A1V-g}sTaiA|v6bWSuuf)U_oggPjfSx9+Ws3kjk<k15``_10m6cy0 z=Rmb(Wo7oA5mtQDrb{;f;LK5j)v5r(gKW`Exq6kQx%FDDAp(e!s1~)~)XYqp5-Xw= zg88R&SwZ<)ueU+|5D$`QiTtPo=}2X;;9f<nWs^v$zpz-|k@4IyS*VEbj4DW640aIi zKsgrnX;l{zx?73+)^zx}^0WW5x;}NQ*LOrsCO_Z=o`$%D({ho7-a3(3=^cID$Z&X+ zm5dXick&wS3$g2E)uv9K&u6datF@0Ih~v{YrGhshwfXf=F3Y-0y3Qj!$P(CMN(ucD zdH%mO|I)bcJ7!_t;7>Z5@WbpA$zzLSQvAkBq)j#Ui)5e8J$`yqTIb{xs7DEY@P626 zpEMDgnw?d<bDUS<yz^3iL%4tSq1Ldv%~r6Ao0a%TyzQ2$`EapY<@I{3cJY4T#gTw_ zn(;#~-jf)OPMTy-3=pA)+KN>eB}i~^HSI33$Cr{NcUej@_Up;0%njyJJ#3tM=xx<o z`)v?}bvI5<P7q<7I$gsA%YzLXuDS?-l1|k6Bh(VhYDTMWQ|S_9mWH*hST+$Xb|jV2 zZZhs4ndJPJ_j?e)oAwc#9&gk*M!)hrlW9}r2yXYKF2{sS5qMZ5`L(iK>vnN5TTBcz z#UlHkaz6T?XA9-~cNo3-XFJYfv?+)Asxt?H_*SW^3Uz##n4FkfL}!fLn4>$}VjIx^ zo9fO>Fp4oo^3}Y^*l^B6;<5Si7kJ~DXfS#PYI*F)jic9ZcjHoYij^nC=cS-v(ax5R z()~~%bPC3tlYkQKwfKSNF1__`1jLvFbQ_3ier|U;>w^r=^K&jGjroi6g}Ags-Hm9z zo%XQmT*@i<)QD6M8YqI73pnJd!g8ft2`dXUV$UBgrB@stpxx5E$AH?LlEJ44w8aL@ zv7q9YC@L%W@o>q-vl@;qte8@M!*gBB{d4IG@0Snxh5ip!Zy6L<*G20#-nax0(zpc) z?$S5}cXxMpcW*qnTM{6+1xSJhcY+h#-5Ndpo~rNMx?P*9{z29Lv3u>c=A2_ZV`V}& zYd|3T__JN^j0OOZtC7^8+rO*aSk0KJj3C{|ZMevj6nnfBEfIAQ3yNhFM}(w>4x@=- zCex$}SIr(e-cwg{k@RuGwQpO0i~WT`?I;1PEZ{E1K_oj<;~=)kzmTKFQ=|u40ZjGw zU?9Nqr|5mZN;}TM$ct9zniMH&z|?Af<s65SCs_;!8l=ft5zXDzn#evgKK$b#P-{B@ zN2>n89%n-_3a%uYN=keE25CsFm|I7vQbst$d%xcC%FL*&3Pt9S*$yC{qow8%KKw#x zchR(UY>OdTd{Lkp34j<$?4*Qlt~P%V2z&%D%Q(&b@45+r+|=f+ijNp&;V#cdnO?LW zkE35Wzd)aJC4KavazNCATItr^(t6%@D*W2w<j$IvKfA{19&93@xmMkvuin*%p39Bj zbU(-d?d<K-TCHZeZ<Rc3kH0-kcacWGV1cf|OnAK6yM_lZI7i%}-|PhE0_=-w<0Ika zZ6WT48$J6v758!e;sTQ~XU8t(*zaI<rwCI)w3%w0bQmr?Jij;)0>rZ=(`-yEMhL@s zQ~jgLa4axMa4=3Jz_Sp474zv&3cMmx#Ye`XOq7y}HvEo4CWUj5yUGHHx$OO)L5<ML zLq-rbq%57fL&|O;fU)0I&iq=M@BTD${e6ZyXlX7^;hii~rmIe=XJ(1oZLLB<wS8qI zD0Fl!)X$HTDK(B*qXwTU60eV&3GdhRJMUacdeS8S!tLY4P>(YqK6z0D%_4#TaIE;5 zf9v+cFiDtXF*ODS<_~Q^bGfg;tP&*>9tJkGPky3P73(NG&KM|~!*Iw;#6WaXgeZzd z7%xtWv7*&N$oNY`!}PTwi@6+Y;xXQevf_N<7n6@a1+p*E1$CfT9|8nI9&RLT0K)vd zQ;S#$`SbL>?ZXLb_HmI}n;`CEDxa;$NZ$5~HU3yaiDHl44z`LBEif;G6vSSwuxeN< z91F1Ml|;BTyj-31|E^b+)W*9otWN;Upr#7)cRfZQ^ByVGhPalruUn&|avb!0ie2cm z#=x;CUbQH=q1GqkE=IOYc<x6&hViC23P3n(91sSO2)v&9?Ll@~LxIVQC$DMs@#$<t z|LXUG7vdwA;QFlC5kNgel=AMY4OoOdthq7di1#i~^j0Vy%txS8jM;spQga1c^@R}y zU{escE+nRh7M~VB$RvFejtYQ-ikqQ6VFU(JC92B+fni|Rjl2SQI7lvCZ(qwE*r@zH zm)dCN-x*-q&jJIT0#m9?i1K6myDghNgqz7V1Sv28DI2<7{uqW6|2riz`wI*38;k%3 z1XD0nCn?76=ew0lfz*dZvBQJ_AT<1v@RFVx6F2}d7re>)3t<T$HPID7EN4lx(g+m! zd8`<@EQ1;@cQ;nS1}+?59GgG82oV4j+ovXeEk{sb^3PYd=a?-DLPXp*1S~V@eMUcX zV-qhtVX&GBK~+F4!A~9T)5ht*`R&FDw@wHM_rNf}Ly*{o$Ne?-=U=^w4G&UdG8H^c zDlk-Tg^4P1-!C@&BXWPn^|a)_P71ndOSj*gW#HoDzc6fJ*n^wGFOKSMYhX(#=s^O9 z41gm6qyoIOV^{djX!A~`zdgNCBad+Yu_|UkD|LAW6Zc2&03}~<=CVlvpr3%gV|CXF z+1AoxWQc(4O{P@6VZEdsnmaSrx-!K#IRr6s)z0@OfsI%i{Y4sS9yq1rdBy+VuVxu2 zF0s8?7Uq}7E;exA`V8MVxgRbg4}CxK{B-5<iyG3kqVaUAP0aZ4u^CQF$<&l#@gsIc z27;W6$014`Z+0g<n52%WqQUaFm(fbA)&x?;s47pou!Dgd`6eHE2HG$8f$tMv{K;>H zZRxvk0NRU!{5n=%k&^IVTjEQm<oyUEy{}^#%}ORPM#chv!9~)>jt*z@4N#PZfG91H zsSqJ}zX6EU@Ff6yNfo%J^NOKf93`F1Q^wk=`9tDjGt(D~Y6DF=TD~2r%y}NABW1b2 zmf4K6onQ2rG&G&4`3A)06i}%W?GadA>=gjr3|diC(CkoVT883U;1`seW$;#R1V)7N zMLIv43Gxm`TJTjYULyMM9f@SKhPin^y+e3>L`~W>00(?6nA$ip79a)i5>Us9B+VF} z!%LG){)rGa$s8IEXC79Bu8=Crt=b2LiS1-?<Z<q1O3yU^w32edkjKKLarRWo6x1lC zTmIDmF$~%RIygXUIxp*?tSMInM+DBKN_q*cMtmAhIERzfsMeywLemwY?<_vq$fmU~ z&xDD-<Z+5<>=kFP=C8&hXBL%KDJb^AXtwAA7)bJiUKL33iH+JrTp?$bBFOiBi`xIL z=@3>V;KW<?HyN2Q*)~bBsTp2ns;{TJHS^2jeO$GR`EOdbG+#|Xr5MyxMblJuzl}x~ z`7{k(<n)yYzz`pOF%pTGCI5oYUsZ6y)%jRRu0~W@Xx^zk?2BR5@nMU8>5JU#S|!U- zF@l8dwKZS9VaJsKsu_pN!*QU0-_s$3?G7_raKVitANXGUHzNjx;HZJ9{emed-F@iZ zQOVB|*Se^f+QBg~$Sk9(b&0z^#m0srgmT#-;{puS`NkjnKOK<!)qU-a9t?Zvc}C<1 zs2|!%0aL5UkCYIDj?&NLCy7z7Y>6vjgf!3Psto-sf);8#-+)5dh(x?ApcYl+MSJ7V zr?M1rATFnd$bC8`4gmfDH7rss`hfBh7+IF^6NeE_fk_pIv3H9Tg$91#5C=Ydm=B3c z#T60)#zAAmQ6p!SZ;4>4=KlH)KpUwF2-`$ZL<D##p~BfgWI>#>J>omm>3^p9TM}dK zWE@kLy>q+Ri0aIU|EqFy?BGzOK2@lv%JsX!yrY;A3Mt58wz<d^dS!gn@Wq0)Y8C!! z4mG)=NOT&wnI5R2)H^o4Qc}PKt*~YN)NY)p(C#|UDIMORaML<>g)A9Fkkp@I{ZwKL zaYkxaHV<Pwh23_7M>{~yZ^%a(iw0YX=#~H9Zh$lI_@!S5cX*V{CH$Gym}U3}s0CgF z9c*~}llMLw`tT!DhM;jomY<j(OB^sTA9RTN?OP9L_)l&n5{=@*#ecRI7G;RnctEC& z*C}P6yt|K4>jaT~+Dh#u%ld~Tm}b`9#Q4mkd?x!EiXz|2RM}l8vi&iUG}{UJN){>o zd2ZDx98wNG4$^*Sh%7Fi!C=w&{O2iyloL_pc$=KN#GJ*wt07dlx(%m;7}*`8e5r8y z7WAmYd|ebd6#s4jnaD6MiTV(nHkr0%h?)MJrOtdyH{)1)U(9~tFSgIbkAL_=vte{8 ziHJE6+{Ueb6g?rlzbYoJItW!1omLUSVLl6a&nU_rH^OYfj|K+@rI)-0>jZF8z%f~6 z;8G*PlWKyftzu)rzW%yNIN=|)82;sJn1eu62Y!b18mdbtkq~N2Ml~dy9vw6~V&Wo6 zcu2kDj1B?Qd{~!K=qVg(*%*gv=p3o4!u$8{xpFehRKvh~DZn(@M`jElfD1L_v!Gb# znY`OU6YXM<6pkF@mwh;(WH=DsgVRSGW|xTCOAW5|2}sUp#&1jOny8NPz>n%UJ$A_L zl#%lDteX3)uCV(ncG*EennCVRfi}Hx-)*wV>HYuP>|tMC>+ve^=TqT7-)dp))2r@S zV2#7;K|G5Y-!Oj_K05@-#x74ILk$du$H|L->Q>tY(R|*Zriz49Q(lf84os8<!l$-c zA37$ABf4*)G>r_K;XcHpA2NRkRcDrK)e^z`j3~M;$cyfjeci-x5O4;;gz;zyPd>fe zS~2;RO1u&uPAaa&>@0X0MX`A9!w-a_ajVWpe|m}E`3R?OR9ci!lLFf5<l4a|TbuyW z_An>lhD#MDCNqCDX~hrU)Mw_oW&gwc`I%@)_`ThoAJ9MuraXC_*MWx18cbMKqYy>v zqp-4qt5wPuP<gy0gWJ&%i<Pq`M^xAtVu=x<HZ+_ZjD_Te81={{&S8cE84udOO`+d! zn7(6ZRL-IR{4Ap-7*)+zcu{{)LD1pFbKI{`;>8&=Etr4+X(~k05pOIQFa!=?s*)fS zL@5f8l@Rym2EovaxG&9Rk?LWg;z($Kox*)S@btU<k0aslxWhFcrFNu;VE+TL!vgtq zQ|mpovj1zuQ>d|$W~^|}^7}0<RUC;tWDlZC<h10@VYJThE8OQ#-uH_(^D87ul7Z9o z#xTNy127^?Z6&%tXbNTrZj0>&V<eQmwHWS#9e8#W9MRuJ?r4AR)2bHehfj8R+(EnL z8n%q&mX$3&LnsE~RgQ=w_&wIl5vqcjMjzoi>V`zH;TTyqRTlqO0Kh7Nn~=yWc=e#t z&$d(f|I-3Ul@i56H>m7bAI0n0o3$`cfmGPDM~hTFXw+s#y%6kXeanxGEIj07s?>B; zh$!V53LExu1(rQ*Si?vFv;O94f{$ad;%RO_DmCddTQv?n2QM8bZwo4-aKN;9sD4wO zg?Zw?<^px;O7frSO(Kc|s3C^?W-m$L%;QN5s=mr)KbDH<RN13XqVshh28h#54nw&@ za4G`)J`gs2Ko1wk7+%|rQI(Z={!h1!8)~n)U-{IYz<~+2Ol?v~C_ed$6vbc?3_|(0 zbHlOY*Jz2`j{I~ReN#^Gi14LvUWrM#gp5${rf@~kK3Hw6kg+6J6}$LOM$UbN8A<g3 zoJz)i=11WVF9PesrFfj7>AWWV6mWep0l~|)e!Hn@_@L;&G3-cwWVH(*i6m8L0YsW6 zyChEvjtWNOfCINXK#xxfoMW+L>YHe1?vuBURQ#o`Mmj(^cvyJ*HUB>7>lJ|t$TD7$ zZ4DoS^P8JI2dFG3ol0ZfS&d&Fh&o-7s%}rrZ7oe~@q0VR+JHHf*bD>&^qYSi{0u8Y z<xwRN>(V|T7Ap1RMbxQL=L$*|ZBr@br%h@vDkwD_GVswxPE9Ei1Zo(C8l;5`8zH}B zCIP1ixfPhut2e<C;j<(a7Y7<D4;?fRb^Brat6)9Lmki`l8anvc?bZs8=M9w~X3%J! zJY2x{4!42Q$1&bkq&JV9mxmFj`&Wmh=qQs9e~AmDjJF??Fj?K`(d*baJl;Z57n{u_ zyG4a^dxQLsHf-i!LylJh-H#XNxnESSXh?%!?^bquOHiBJzSS$eC6mF<950JgArB|t z9m1`i`RWngnr?(%>}{t|af8~-J0f=LoN&MrsyKjf2&kUOOztxzt``mwAgdWth=Z2{ zh5H#foG17t()79(e1BVD{_B)x2ReR~XFA-c`AE+xb;Y9MOD2E<hadZk(c;P^CIA>3 z31>!z3SSXTkVHovXIWYvBwclO%JTJVr)LU%!cktXkk9dYPoqVt5vuwi5{^BPmSko8 zuQRh#02vZq3JuYwo2N*MDl^~tRSE|f6AfSZpW7KK?9c7Y{S5UN+>V)1p&cpUz|Wc1 zyh5Exfe8(qlcTUA{+S@I1B>BjsSNi#r1A4Faw4_A-pFv@<oe$gnvul*zBZinh}y)< zg%924@6qA<_<`$4Fh4*pE>)od=76@CdsnzEP<R0W!Mjq0;((+;{`o?6$L)22<9nJ4 zAb6^}{iWmx0Q|>-KXOQ6oJ=+-()M|fVhFnelUbtm$0jNKr7-K6uafo}h_WgGS!y5^ zqj<De@%hvQ2@T9#`MX*Ib+(1MQ#A_EWUm>Q{(~1oa*M5i&M&bDkys%oJhpI?3`v&C zXlOh(3US6enlzCEEyHyT-gKhFK?om6qbbw$4lIoSGhFfk$Eqw<PRhQ3lO)gM*XLiK zKV3`$OTeHCeC}_I?8Fw#w%|ruAc&3(_CRH7>{PkFmV+dMB?J^nDz2~ri2hhUj+5T) zePIaF<*`z}2dJAA{$wIL?S-x)H~bfgc~r=8{jlW9*eh32{NH8BaPhAze#7Kv{Ds@T ztN-vnOJEFLI<C+~s`cF<s`nv|AmU*4OS-nZ8chgPMXD9Y9{3fq7{&-;`568Z!EYVn zs@ryBiyZ1Wc3s&{mWbP5E=;EaeHsA*aEBQy7y$yftDJgcoFqt43Vb+$Z~)AG&Ip_^ zQt{2l%lLx?6W|pi)(EG;@_bnBzA(TBN?P~;D*RATIQ)5C9})_F;pVCr*}o3_xPeay zB>aGU^>XPrOZv63D0GnHiZU%804HI9pD4B`2DhRILv>_s=E#o%?htP`K!P!rO)G~2 z4eQ{o1V<kcGP4Ft3&khR$%Vs3oD{I5D`%P_kGjz!4cmW1y(w4!<Ff?MHkFQ|QaZ#$ zonDLf3qTC;FYH9fN7$DFl9Iv0C`Z3K3MN1(5xI%Pb*E~@h!w`EaOQ%Uja=US@i=%7 zNuc3%$aO(Zv^ZYeAWdB9>BEa-*_-W^{rcytBZm!ifNiemcJy)Z>hqg}n9bzl%79Nu zwz1!nnT1R4DSYVtT2hx6ncHz7)_U9G+4OzBk9sYEry@Era%+O{%3Uz)EK=NLuV1}r zRc~bt&(V7K%WsBk;!}NIiWY?f>yr%~)UDvae~^;wanImI1rK1`56`>m*&>76uh3Q# z7$>iKrN<{krSiXXi_n2spm)26^KJI!#t*D!a67bOyUSB(_qKDRIAQgw%as~FAIqH& zJq(?NT3Vsq`qOn-_hy=YBYn%D2i3J>wcK$_i*c_qbnxFZ3Xlp!jzZfAmXfk27X7<T z_OfFWiEbL5*;s(dlc)VnzbjZW=@`{?a3NY|$Ri41I>$kS$A&E9%SEV|k(H&bt?hmC zx1qeezwyIFM`Si&)34BJWoB5O$?DT`euW<y)A-<cr8*NT6gFy!!6GkBZ5I{=QKWR$ zv2f-8N%-=Gd8HM1ngb?1sOW7z?i?Q<kG1`e?O*X9S;A9L3CP68mNjVs=~8PYP8i_K zNj&Zxwz3uZh|$N9(~PXBVmHeT<2VcFK;~c~k-^VSE7P{k-@bjw`7~(WfJH7S$Fa?y z%uS={q$csTQuGXpuglpKUP3(+fi<QEKEzhEjm$0U8ev0S{u8AzoLStug;jQhvGgxf zHZGqAybFRrK5%?zp&H}G)T>89ZTsx<G^W31r5W2v@&)nAI>{f2s$e~$nna<-Tua&J zWsP9vs9TlkDpp)7V;>iu2n@slVY|>GG6E#R7@PA8@;z#<(cCKdC*d_0J&dw}b0&J; zM317-M>QIOeIG`inBFWTXVOI62t0)_l74e!i?Ix6^eZWGHpYrib^eC1cNH@3$0$Jw zYF8@6eYX94be+YA>Ox2PXswMAn?MyzSd1x>Ifpd|p{afj`_KCxH>1UCGNWcG-b$wY zQY4SbZN}lyPHiOVO`6Jxz_X&rYL!WGHynuiQB8<JY528K+w;Dtp6?|?ZcN6RDV;#v z(Dt+~j2skQDYN2@%EQ^J1msfESoj<}K!eLtpb$kLpsi&6-I9|Hhiig;QfagHWpm-( z-H8@qLhJF5(do7^jFYctBRcHMy*_py)C6u0ghW?LyY;TaWs^^|c@KH!v%0R5Sg=Sm zaZ_l&`UqYPVqarS0?fB#7Rh<P3sbmkycK`lFvxxayg`@ZjA+&--)KW(GL0X0NCLcB zk_o)lUov__Hol#S2CW~6S~b{q)dl4HzUkZ|1)CNSuq#vOy8jfj-|_KMAtxLqS^V2x zb$pF>nKa8zz7X72laMjr;rIH^Cgn(vG$Hej)ax!C9bDb+y2vx>_s~3z!2B-N*6x0d zVeJ;W@TU27yp{3g@VZ_IMP-$*^6WcXK!NY$CCyhmoDXyMzo!3~%sU?KTCjL`{CnEa zQNb}MYWu<G<PS}K=gkwMWMw`sX~8lmdL!02`8rwYbZ*u5k#Ary#kugihZiSlJ+bem zReddn3Uotv)z8Hp34PDka^p!S;jI+<SLW?s^P1dwZ?@d@rX+)ksHN$a&KHw6Rw<gx zEzM=RvB;l2!$9?LWF#3#I3iN@>khe>nCIntrpS=im^mzW7#h*u<HMU)v>`Zl-!JDA zy_JnRY*iJ&=u5XsNo28Rxn8%oUIwka+}!2rHI<&snetUIG<QLR(NOE`_;?bmwe}yZ zR&&IS4A-&=_Vfi!CBIiyWvJWB36TKkBL8^4xKpxVW+sx<9SbBkd7mz8GD`!w#({uc zGFBGS1UknR<2GmVvdUHQ_t8vwiu5oPh&r6+pe`*QYz4dJKR)^o!jdtrQ{W^$$*MX! zmac`*+Bvrc-Km6k>R^n)V}WT~Zqg+EG>loxXe*Nc2E1|lgQ8JC=j9=txy6|!j;Jd; zVwEP4Z~Gitj>9Nm06!)DAvKlD5#_BmxB}z~(ji5gul6_g4i{VplU|3Vf#%j+qEH%U zIX9)Z#I*!a1BY?dbjS{tJHxNO8H*L2KG#j%l9$x0BHWFbQAmIgs|h+h_ldy1t19xL z;fP1X0Fd~z@N;zy1+hg2X)GdBDwEilEllC^hl~VJd~!Fk0$yXF&J&bKO({ZEtAn{V z5PGnY)TI<4coLxq)O0_qe_&C7`b{I%!8Q2-#nFjs_8j&cZ^>hrs1nft%pQfb(&P?( z47#EN2Q)*0p_gLFsyGZU01M?hJ{Tb--yob*X!hjd#`U!O!~RvfDqNkF8!gUI0<E^Q zqz3<y*E7a8PX9{QaGm>8leNZaR_$#xfjl7*NtQH80v+C;QtF{w&SWQDdNUbji<M+W zCZuA-XT8IxkD)jR6|>{54jK`|pc)e?-M5#UbD{jldzHPAw>z!w9_VP?Vz46u;O%mK zTP3*VIsOoRUsR~)E(=w|cr6j2D|X(&GIT2V7*CSd__|`gguqbr;9qqtX3*w$I6B0; zE_ArX2L>8`3wRu}ptw>FvRZ#`n`kK%KAQ`9&Oe3B>F|AOcg%7iB#s>;T2$dbzb^5a zFn-?Mh<<aujQncDixA4UUl|wjZ2vdH!-01^z;mOhPVlUA(*4u-nhKY7&(*WONq;LZ zZ5ws>lV(f<Iw!s7zYj-jF_e6QLzlmeuBX?k&Ra&yL!PUl=7J`MAvaKyQ}>Ow_x5`t zy#D_NVBooqx@fzS<1Hf}k;gA~CLi>AHoF}jgZ*|hVqj|PL`1t+-{m~lgMPKk6V@5C zIX}MqCL6uufS>S4A*dzC?QebWoxcSIEGHh(q^w`+R9@jEpSDPZ!#5{&#N|~M2whU~ z?{^pR)xU23-m+sTdfCyxd?G;ER7yG<?q#!6hRw`8`<`rkO5Q}S8<Lg}#EQPXzI(gc zr+Ip2zCAuysC7JL4987kK;^=}qK)JL0hX@k(ZSC0B?Z+zPahs!St#3L*0L0KU9!;| zOAL_U#A9sgjs>0RGwmA-nxC0ri;j~d4h|k=jck=ssVDy$#wnkh%TTVedgYzvCLfZJ zBB^!K%R+?2#v&pX<Aj$14%1SGk>CMV-(XA9J3lQA&zcyvuUdFz;*{3Z{NWyl*$<|s zHvNqK{QUIva&Ue(bZdIZ__J{H@!8CH3l=2Hlttnk8vZ0s7}|#2J5>%ABlFYnkm&$k zj>LlS+T)#E+MXUbw*MLSa#2T}SzZ0m!Z0u}029hi+iC(~FkHJ_iL90nH<{h6=WX2m zEAq99QD7?tWU0-VdK5WHDtY=2ZRc)Oqe!&Ia(@i&DaNNH2w4DsH3B^&6|Hz}n6<=m z4%HOr@c!+OG^o;kf{)zXZHm80CvN-Zr#Z!_M+q0fA<8hADbOS01hu2enps^Lg&HUk zFH;6XnI6{0X000YRiVH!MltZ)zFt$iQd}BB8wNfx9_dpW4pcqc>SdArno5Y#lrBjK zWW}q{oP4I<NcLdEyp818y`iF`!f&qVwYKe-d{<IIpptzTo)WbuEr)kAq$x&3mH?UC zRV3O$Wwt#e&^%@~lRXHclZN#kshtNbNQRcwbrwIAp$p5ZnyRIL)*fut$Vs%*yDUZI z!Ax7p2^I^;SKG6O=@o@?FrLBGr`VmhOI^zEMcQ?GQ`0Bk>vY@wnGr;EdG$~UYTHI? z%Wrf@5q&ZWK6TrBpVF(900=z2+DmN<xjFfxrN`RYX1QMaw{RQ#P$TW6{mE?`nTOS+ zN5JNqZK3C}A0@MD&G(maw?CHH{vcOp$m2u8=*vK}-9n2^Py2RI8>M0XWG>fJkxD(e z$3T)S))}ws>a7li=F`bL$||^AqHzPY?hUmkPgdrm&hw8JUGpCdQZ05Fct-6XO)k$2 zh%l$bi%DE`)c^Z{dwY(f^}1BL9l$(DJvS~P2Y*i^vmJ2d)*9z`t9oyy$7vel347f4 zhQ1<``<#V|EE9N9cwIuDTmlcH>W#kny|%Myp@{r2#6J@R?RJrU-<YDvtcU|5%=GBg zH)?;tI?FCH?%9xJIQM_si7@tm?(WnEU@X)IJjL%%zIS_*FDuOTKTbJz{Mjk(ljwW$ z#|04(+Vt4)F-4ev76EXvAQE)Zcdzv3^m?4cRoU%C!Ru!Esf$5-y~$^~Iv|Lv^Xb;< z>7F@BX2pxr?MQNiIp)<-!UvkLpK@kI$WvCahTZ9+ws)=gNP8EcN-eZ{(@!;kQ1`H` zI392__s?iW`kPdY=#Ur<xzdj)4Ids<s?-+>T^7JhLZL{=306V;Hl4_w=>8I=6X2&( zy$OfA_<m!%)!+yUdGH}7QSwTMLznIk$=-;%7uR&l0fVP?6D-P^kGS%lt8Q1<Uz>sw z-f!gdHm#`1Vt)k9`ETbj=wvAcHoyS@4{0m+a>>uf?#B1ff_L6ul=Zb6-7hkH@&K@a zoSSfpL>#=li!{v8+YMoEzoOSn=}W8^toG+x9x}i~BZJC%w~LniSTmkh*Qd_M$EKk( zRU>>wPcr|TH4`2M!c#hS!-tq)a`V(JwgAJc`amH*XXv7qh0lpLVH5R$$fO^;(RZqb ze=iPL6uJ~`)Te@&y=Uk|fbW-GA^9CD^^LmC<wM5ll)32+nLaiqb3z~Y{&`(lAC0Fo z3l>3xkynd0ejHsGb!HAG2x1Fe{&Jy9_{)D!S#>^NMoY?i+A%rD-u6$uJE`N3&V&JE z-EMVSa+zTnTi|Zk%zR*mY_iluLs*Cs?z@=-d6t&;AdHO6O$uZDu;-`1!~jv%e#@!S z5p}uSaDZ5?`()<jr^3wAilu)+A?LV4_uu4X#%$&9*HQ`ugVxTENn4yo0(~#1j%Ivm z^_M-)J*rrptQO2qzX>s%uD9@$9#6T9K_}Ui5uAgMzHd2eSa-tmabj$I>i7@GxnWNI z<KM=oDSkB)?1gRqLgz(lT0wiBC?Kxu<!K?$b4cRn)R}dQO2d$W{(mB)TMcOxPlgvq z%vgZ-!*TR`(y1C+YrVLUFpDZ-p7Z-p*{V2Z@}<cvpK&EhR*`U2i#HkhffKPsE{Pyj z(2+{+D>&pA)0Tu!D?l3UOv+#babE||zk8vrw&XFhQCyb2(r$43QkNEtBYXxgan_!* zM?WXr#~Q(zCL%JytuzR<=;!Nv4{PcPt_vk^!(`E$QoA{jZn~*n02r04v;h$hEfN(( zvQi6My6+xdu&*8XTxyq^JG^JJJVyw@<+F#6kAs@bGzA1ess`Iqqple_+QCl=c=Wi| zw0Kryu4GjW4Qfa1H><%T0l!<FhTdWA7w*5o=EeXd<{jvE;gE>R76Lwel&!rzSgMc1 z(rw8LcC0N>R#ZWoy1S{3h3PtqQ?h@%j7*Y*C-pQs9sR7NyD4QZ*WU}zqE$liRnYV_ z3Bu+7bDU+_{edcMNX(+&^k+4|vxA`p%+1dmjvfXGGwFu)CXX_0A+?g6#RILD)5+#4 zEwTFF+Sk8Q^O1`nrV>FV%XBKR@j&5#@cv?$znlb4IZVA6nFnJKHkXXWXJRe)zy$;0 zIh}e=<g<m6ZG;L>{gU?uetV<_xM!yNVe<RSA*$Z6Lg(r=^dojD5q^iNB5uNquo+?t zR4HIc{Y?VzwPW0txG$*EinxIn9*UzK+a<K=a8;{2PH>8eJZsdab`I1t&82<L0GMqk zf3Vye)v9%XHIS>Sl;8q(DyoLbB7Ep2QnAv7cBMfq*s<*<@W5Jh-{AN-jDv;rAESAk zWlVfB#SoohcF6VV>iPb-L6b8%2aQ@rbCizGhNV#Oz9MP{>+oCqzWc9^{qGC!gq$Nd z#Xc2vcvXE2@B%@Mxn2AYH-D*<m|`xwflMCyR45>g2z8zNX_Ak$w`{yVA_v$fnaA~h z7V>M4*yn+sO9!)qK9fta0SO_xhzQYld;oxM5jl!1;oJ8q1c2Ol&%YFxlppu42txRO zI}$Lj_gxM<`-;us0TALJ7Uz{n*V=B3q<$C1JSv7kPl@xlEXL5|=Djjyq3;F8Y`k0H z0d+YX<rxIN(hXaG@`_x1?ur<^X#wI10VkY+50E2bIE#xNp{l~hfGB5UYb}IFBG%w1 zC_A6EaF=rs0v}(U{&AY_J{3*$`D(ZBIYR9*0N~o!XMq>@rC{>GvCY6mw6u{xC~Vp# z#-oV;GK_@5C=e0)T)AYz(|nO{n-AS@TVI_Ex|(I!fNp+|ueVwCo7AsrpDpe=tPq<+ z2lSYqs^p#SiaGuRn4dltwQ6H59})#*9cYRLEeFK)F(k*fJ_DK7Z&w+_-rO!_+fHuQ zDr5~G>iSEU!tYS{UN56&Mr~k*@fWwpHQBq^v%rh6U61t_S1x$*qiYhAmKB>`@*|VZ zqu(kh8`nWjebIg{hC-DzNB~m=w!F2^%2n^zg__<e1-Z}T+Dx7V^vRBDkNs#pezH+| zUBScp`{Qb*L%OQF$@BD!3nfX*PNC4=nfuTade#K=DOha7d)>%G7|?SQww(g)3OW(n z|6m}zeRjFlbjR9omb5Q+9CR~{K;n$mV%&UBRd&d5K4Fgx`1WEs_?x0k*2frnFtGUH zrO!jLb)g=*Hz0Oe2mk;)CWV3>VJBa&$;6b+y~RnP=PyNfJ#V7~itEA&B3@UNMQfp< zUSdHay+fHMwT5k5O*KI_Yj;!8k`%9&mnubT!WUn^vONx=!EKIp99C<Xuy<{?_>Jy& zd!4>!Lel~72}1-E$nQr|9JJX#2hSI#f-(IzR(W;{Z{*MGzpQj_H8CiYSOT2=97fs` zdSP6_l;CH(g*6&NW52&0^+z3~;|17%dp##&nn(eFd>7FN{Cf(a{PC`!-<OjNRX;i& z_66?GUtg>kKD{g>**<U1(7fL4{(LX0`@?bKn&_mbRugOGbKc-j1gz@UhXuvLoVJR) z(cah7QK~BC5x0W}o`G8^qdnik6<z(R?~6&1uggDeSX|E9e+7W;Q%%$o74O8G`8Zo! zV3X%=Y0A0C_q(1;+10zgUOp=c%b}*iSI^5%OvQEn@z359nf1;1wlvye!6UeBbz%pp z?AdIui##G?f)1+$TpAStNfi1|1N)==p=}SdOYhr)Uk1R~q4l3d0%qCD?&p47TrH>s zAzd#7ziI73stD`#nl1+7lv37if~&q77j=2AD-JEC6a@64*ugRjU&q_x(v1z;&6nEP zUuygZ1yBmFAJ0Flw-WG3==sIKO8RmPc|um4aC|w)yUy=?61M48F3Hzzp{zlxg)s(H zV=W%A5UqI=iKU$Rv!ah#a<n5OBk>6d@8TlOrb>Okm6z+1?u#rEf&oM`Vq%EQ_@)rv zKq{$X2~&Fm_H*4VY0Kf}UyPYHlWKm{h8kZ4G8Hmg*;&}x+o&7Jk+DVgS3Oh&56gq@ zmTaze{#Xnpv$C)#(kEc{{q6K}kd*AJRLaZEo8{r*`Kn#}rMA||))uyv*eg^B5MAl= zNM*R8z|H}9ai1&YBgCD7zY%e!IRe@7fS?BFjn)r}^z-Qz<*aqEPi19gNr{9Y5v=4< z^ojNqWSx|V{Ar^raS)D>ZIQ81cTK6SO7-?9Tona(SMG3As)i$@gVUjL!hz0HcAqwb zQ(P-5R;~$0v!RNGhhsSP9}$Dpk$?GxoJay0sTdyZ0U46&t&9Z1ogSSun<C_RGr~_? z6D1uYbZg0k$3I9z4MO_h`H0NXZ8sNAq>3;};U5VH@GWF`XEXeXM7_7tr}4}H&d(u1 zJ><Z{L^MZBEB3?<&wn#|m@e(BpIUheDN{x_CHKHPdxb~{uLOl_HB9!+I~2)WI3LJ_ zS$0;U;pz#(UXPK%)Yol8CuGv?5XIr<WBGNbN&E%PT~;BeVq=b=-Cr_3Czb$))rCo@ z!#(<8_?z5iC2iW7lE%^?X*$5%+`_^<pF&ZM95<=4E>K;8sSKH`l%5M%(QB-<S?7Cp zWAUaI{6e#TYZF>CNS`W4{~}49CXH%qZ!15qBAD3qy8KU(QZ(SxhB1ohPLkO9R2{wp zr4T=?J~Zq1?zM80wD4$V{I?C4#uzV@q=XgPcJZocy#6n`H*q2y+vjc|i2A>box?a6 z7zZL+N)78>k`l1_Nxsc+ZhKTndHL&p=(ce^KlEGBvdekT#_RLz)81*obcc|_m>e@! zCv@H}8$+e}!grdg?bSo!s-rJ}kb%XA|74IPFG)8;V!AL}>}@2;LdY^_p-WkN^>Qp^ z-bwG{aSyJ?gy>1`q=DNdh>p&vvwwR)`h_0j;MCcut;F?*P@TcmrB2+(>=WK*o5sGM zuS0JQI@c@cg#6MGG1+x5_DTp_3jz*Y-J|Lr3yy?S{5H4C>uboFPBvBejxNUmv+L|6 zVs7^fnwT=Z)3S*t8?{dpL#zI$|7vs!p^TXZ5c-7eah847<%2<%$D7(@ux0;8S*^gU z8KFF!d>5NB^CwDSVS7RX=~lLaF3SWtZOj8BTr$Pq-+of$=RkM3cu<UkX8#3^vcyn? zK;^r80$id!i9ii^^W>vnk;w$1vFpOqPF2ALLYVwF8GW6#k7mILSG)D1_ga%4&b^+W z-X@HY&pf8i$Rmne8unW3OfWvXYMLO^*57(tq=*o&2OMtGhwd$L_0$M%7E8!9Guk=o zKd;3j>(5w;V~cHttM=KXZe{sUgd~ystZ6;@oKM+3bYxTHe1@Jm=3M5}j|ypJ`7FE5 z)Sxu`DEgl*cWGpkGZb*scU(M0v9)g~=e2r1RQMF|H2PFIOJWmuSqQ=Z4Ef=e@)`EC zN##G7CPf6~emh#~C@od2SN>BQMM@pFRo>?P58GizTkk&DC;{jkv_GBwuid~|z}|MT zE@EiSb%^P5u{fq5@qbzX${@*&P}zIo=g_}HF2Sq7&EWfmUebG!z>Nc+NnxIlwghJp zGdU~24L6-ak(2%Vjkh=(QNO*4XvtUiq027s^Er>UzWd|eB*rAC@$vrk><z)Xe;o<f z<f|4tw_jzH1gt}$c?k~JsoG?|^FjNknX8Yx2n^?fzN--lFXXFtlS91rWv7)3)-h@e z6*k$*BHqF$xkGj9POla!K`$=z?;v;GJ_lNQ>dRI;OAKfKo|}EW%=ma3t_FszHH$nJ zwNNk)R-Llteb#-Y*0@r}hdtL^K5a;ISuYgp3SFmHN+2AI<#Z^Ro-%L|Vp4Rm2M9ln zzS6KZ`#U&FDF}Igs6XL(OtkRkrI~+$DPhk<{I4RZFt3AkX#`5+l0~|_j%HlyXDQqF zrS6V4HXeM)L?pS!Ywk`*ahgm~0YKEm$r)Mk;xp0WI!U5N%8-91SS^V7l9ScPQ=*(j z?(;}yLYbXee?OTY!dk;0KaFy4!_?=T#7I)kBpD=9za7O60ao}x3wo+gCAeUil3bn^ zkMq09>361zM5z2BkSmN-j#zdJ6;ktT5l3w;L#D#bTh+$Ptv=gmekmateRsMNGE@HU ztbMyaMfhqM0bAJh?FC!-=3bT}<mk_(ETz}cVfs*tnEj$r%<Z|@_kjCCs|xZ@H0nA! zIvP!O<40a!gN>W4Z#yxM9eC#*VHp?|H6OK17C$S)h_}2v6^<)bW94%=q2j1kmA^jp zacBa<XCfF8StL2Y9jJjMGgp5PeR~M_!&o6$|E{PU6s_h#X@*vCsC1L)5Hrzqs<ChA z(X_)z%2ZQza(q|`ptpPlUB>2Z3<;#a4ocN1n!AkY&nWr`!+A6%g?1HfZReuYXi#?h zBfQOUYGV8k5&B~sOt5z>kluoxhiP{05DxP2oy>9$AiER(;IeZ(94=0ANL0)z`i1HO z@m_^*CmcxIQK;aL1G#d652J-pPYB#Ue+JSi!fdZ|O^PEsNSs<>KOhY54-6furD>$V zGy>tN8%`;WGT`_<B6o^Ew6XA5)+UlejM^PG`gi*`LNXMAAQ~5sZJ&cSK{HU2Mnb2L zByB{*dP{(+e1@Sk=)_qj8coXl*He4Y{Y4M04mkr>1LCDHB)l7F(4|ggB;|Gdo2g?A z9TO`GzB{y;;NSk{Jupz`l%179(8kj0fq>Rr8MVLs`&}%>3Kow7m3y{G(AI0nBQ!GG znBPSe5tgCFD4&;?TW)9uV?59%q_fVQj`v<a^p>;dbvy1Z7ul@MZ118DiMEp|4vAgd z!f*%s6EAy9e?;OF@Nz1xL;f~&e6*rVCv*;esYDsp(@8llo_9F&y<FLtD1gP9uP$la zv$86PdssA0NU5cX+b_(gQ`B|dcM-5Gvc4=L4R&u&_`1#BowCjpJzozzBB+q*^mKPi z2_<*jCHcM2EBLgQ_B2GFD2nhH>89R(tT_#vG~rflctDLuivoIfSxf)kOxyT;3OpIZ z#m@g2ykJfwjm}QZ%i|Qa6>lFxX|3JH(vsK8opzMEw!;44ClTcTYd<Ms@~_wCvetE* zC1G%D!}hTAZtt%aQodf1(>@c(KlbGogNkmqAdx+MZdv2E<)E@GV}VkKqL(URs3c7| z&q=?$h_&eLjQvxLPgT_&B_A`~!1h`2>2h#UAy4!5Psw^;D?4K}Wk<udx}Mivd03;C zn^ft<e;eMo%}Q}1g@BdK4y8tu6cCEv!V>N&h+w|mnKad5urIvUqYE9m_K5z(-^ho+ zIORY6IXxw@=XOrWK)q=_N6-DDy+CRCa8A}lxa|Ro`qFj6?RInnJIH7MZMHVD9`8eG zO^|{0W-kB6ED9L3aqlJ4vO4QzI4dij%BZ=`C|d#g2XA>U*^E3=IgZ_yn>NP&?<1<G zB%_=OA#jex>bzJAQMT=|ahyD(QSLs!hCvav>vxU(z3#PyW!j95+}3i*kTe75`Xy;* zO<t>~U(V+F+Rnt>9BXuYt%LTquLB1DYYi=IKdY>EjvM%HKVsHgIUAXOFMu`onY2Yj zp{q#gm2b`F<GC(x4U0PIu;jCXCs=o1y=U|xeNy;s(nsVCO5}s_ywkZNrs}YFH~!z- zn~-VJz_rU7rS@c{%D3;x>un;E_-tgcp5Jl*Z1*&N8N263Bj%bgt@g^xVL_tJc<q_8 z#rD2X>#)V)_#iX_>*R5~*V=KN+v4%d%Ud9{hov&4-R)TRJZSc*7{R}FF=R=xt-=3^ zR_9<3A0EKO%ohHM`FwL&uIP15+BWA=Y3_<*($@Kl4L+m`9$__EqLrEUJ1b)8I|<OH z&5Uzc()Y#Ckb~d#722V;$I|6XgrMVDHpxqoAuI8j<aB%Ij4qE+PV>~*u?2d?8q#Nc z=P>k6ZVEIBI1u4zWn5PJz_l<UxTd7I=F=rxQ7Yf@5o!5u2rNa4%X*BFojoOYI@=^z zz+C@7es>BX0fCm5mbR9bG<7PKBn-5{+3#HdTWM`9Y+k}`b#-S2Z6+=-<YZT=c@Cl* z^a_S6cZ8kUuY^<oSklI{w6sJ*bU$RZa%EYOAh&$0w&RpxwE9^sPcm!)#kYotXMfC^ zu*`t5YU+L*(4@wO%S4+oCIqpR6@TYLj7J8?Zi0u5APJS?2x8TGnUWC+5@_N)>Nv$% zcsqx4xVRPge!THiky@Hvf;C$LCQuu+N5^+HJRHwNnt+)pdsmTc9CJNFYQbcLFqN3X zCvjUlq^Jua?HJ>w<dNfe958x!#-p73JG6rhWNS}VSupczf*g<oE8hX@I`Kh}Mu92S z38|MbQf<g>2M1=v=ruZ&`PU1Z;M^eI3$t(YM@F$8T}ca1UVFKUKhM2@OBFmtRC+LI zU@i@SDgI95B+@<^Kbj;5NIZ_MIxMA^!Wq<o_U*;+b2P5GOhfto7kxTUvv<N@VO%IX zrw3zfV7PjG*r1ktJr20@I#Nc}Mbij)iZ4k=q0SqLQ(K;+HHl6Iy7(}gsnH2+X}&N= zFOhjv3Zg##c0?q=1^`O*s!h7({AxeusmNW)&NOgycv<$M47~WYm#gHJgqg6%ZW^hn zrPb9sJEf~*Xn5jB5h_j#0AB52{i6!sq$X|RmtBMsTpsA&Pg}_vw!6<boEwQye?J~V zKAG}3mc3p~m|kP#N8e*JM_{;Nb(G%`>wf)xkHFB&4ML;s91_S@`4KXfSg*cMG3#U+ z_ZIm*8Y2`XM0bb0y9qAv&;9l3>kt-4KKpv0&%Dj#9UmJijK$q}mUOE^!C77QwnQS_ z?l}JgDT%*4L;U)=V>B(o`(R|%#n|8OmcoZ@ejae}V}pQ*-1Ciua>9!taj7){W))sJ z=R5Z}?&;l{l5>Hkx2b$3@&EG>EbzTab#WI$c1ak*pPQ}pG)m388{3s^OX;BRx&K#y zRYYzyy`tTJTM+Jr5zu~o)GC8lA^mg2|8&{xH6J_c%1SE$^E`{>`A~=Q15Yo@^6kk6 zUT6}Ji^!W>RaxEA8AizXXqIEFmJf4SF40YRx$l8|NYGQH1CglDuhc3^4ep1SJZ}<V z=&Hzh_uD@k?e2Nczo!=QfY7Xzhiyr&iJrr}7-h<k-!+*+!GZJ^n}3zRkI}O*#|x6Z zJ;V=TPis)eXD*|FLx-34COxGrwF<soL=<WJe%F{VxUgau6Z5Isemxu-*l7utnCzYu z=zbcGp$w|rznMX0KX--;orYp(zg=S!)jd5dyh|>0HEye5KeHhIY?bPLpo@Fx_3H+y z?X={p)(;+m57vu`n`v5JXP+O<5B=fRntwG4-jK~nbXzNPP7#vA9|*lhXAhb*Y}I~c zpzxU^c*$Cy>By}$Zk-7;Rs&pUvpwZ#dQD*3#hwQJj@-JOT@UQt{yHABF49Uq6`xEH zhft^gy7#AZnQd0LV71*-4*_dMW?4c0EMI${-uZFyno=vV%dd?ZY!<U!FM9f`VB^*E zrO4+<AtZZCut8Z8xy{9}@naOuJpaaEpko;?^CuZ<Gv~GD90W_9cj}tm<V^_2i{I>y zrn*X{O?EblNR4}+=NOV|&e;W=T;2N8V|=>HDp2&i_L-ke4<{)yhyf724#%Kxot=@y z1lvPe<Trz(>;dS}MI3wBN?k_JS1jz(HeMpn%dd$IC+tFi$i+zyG~ONdkYCz3b6!~w z%UZ|9Kek<_on6>T`kTrw`8m<U5q>gq(H`v9$qjCg#2KANN8vCt6g9>w?3l`6kQ<wu znrWcE(FIbA2Xckde|Y58<SX8VFZ|$%@e6sF2ZYjP02mGv`Jyb9K_-vov{ISDIeUTl zpQQ>vRy^sQ*C3SyhZ23HD%@oL^?&EEA($5bn<4#Mw%cfidPEL9#3NCP8WU3qK#SL4 zxVTFuKolocUc*F}N(^$cbv{_Ht#Vo^9DNs8J=&d=XCL{WYU8h6&h$mY5QSv1w6t^_ zjEvi8QKek4b?NKjA!21st;TH|Lq&5>vCBUJ<2WcPDD*r$e>L)6ic;@1ABfLn(OH<A zgH5dcj{cQW62ZP_Wn_HQZyCAHzq%S>HLz!@xb$6Bwy~s;g6T-?^|J;T31-Mif2yt{ zMgpiajhG{j5JDW;FW4bQ<%p8D%7<S9=zd_!gI`^3u<>!=Q9+^NRM?#v8=QR*3+7B! z_!o@u*3<sP&tH_JqlV!yt;jMK73wm6nFKd~P`oRQXLB>kF2>#ugp;vMB#Hd8RI<vZ zDT&*+`O6PQ@Q6kq4fU{$DuJ;u3Wp4)OUHq-<$IjD0U-RsCaI|fp^?L=b@$(MYjfX_ z003!IaiCQ`dU!Z)pDE1nid>PQo>FE;92&jNU$_q_y&(n8(PyClQu7VxGJk-HFKIXZ zD1h7$xQ7mcNE~?8$6Gl-N^FK|K?pc@PFew*e$q`qkTZu7?Ke3QQv(J<$9E$X+|PEZ zrght4n+JX{5hxQECLE*iuhdZlg^F#A3gng#<{bF24FHfx0tT*l1F4Wp*sgGpCzx&Z ziS$h)aqJoKS+&w-IG8kF;o(4x2mq5(kSe!aQaRrHHFRWmnzzi1FMj(T@I7}*z_7?8 z6Ey`SGzBJUh%r9A+Rhg-Ps}L1G>9P+lRC!1o4NDPgkA-7+DQ0f9QP~I*sEOYCv}4# zOyLeIY=6~88@XyKHLoj^TCVpeL$2o+aX=I;f*INGKp#gx#1DzA(BrEX-z20f4f}9V zR}KkCaEzI}(v0qJ_)J^SWe0mNP2dWp!-2-)KM!tNsH`5=1=Iz)?<fV#iv_KDOqgL) zE|*+r$7*9AWVb&J$l6eNzJA|1YAf`0396@g+LvmcRhG^F@z#=U<go#FPvQI{V7!mz zzSAGfPI!3TPk0#__}lL-@yuqh^2Kv<UGTX<CZD6`5u5J~I!~32r5k)dex5=8G${Jf zbPeT(u3q^1VKJbP8}j^cA|~ecXfhWNaPh%J^yxbHSGxD4J?i(pwXc(}HcJ&Dy|(v+ zTUe26-|cRnQ;IrnM+|x?p0n>OF-!qUO)h!Qi<Pq!J<gxxOX?z8xQgELG1qEkIa^M; z^IY=uybh$hnjJH;6-*ZIK4Exc_s2^04-T}{sAXEug(~IgIPc8Q2i=j}jW-MRoCWL_ zAB`<9o|#aH9ZX560T?$dK5a!m^~9bDJbsg!TdUumZJXRH@Fv~3<|F>#`tp8O^fY*G zj!N`z3WNFCOo!MUd8dJMp}bnJ^FU|r-FSj=$6kEfa?!<IaKr875z;`eK#}t)f3r>3 z)vpM_oY~{|IHBHxccm82qR&5iwF++6dr$%2vqPYnYfiR{QdK1g%onGR^xR$dM?cO? z*e7+Doy4unX1#`jls`j+uO^9-&RnNoMbhsE=W~vXp4v9{6sIU;i&{5Wv-1O>kf+I& zu5{fN`P?h03cF~p>t8!r&(wExy<Pxb)2jiCGtt{{7vvY{D7g~AQTG{2-E?03MJk0V zNTGo4mSm<!Hf3ka$N15G>koOGqd7t}ndk2?okE{Oo`BV$z1Ruu>rTumhbrS0Z5a|F z%8-#M6lYNn`+6aj^_C#(acdNsT0+n%I!{LRi`m_gH^P$_<6)CzTffEat?}Wns-)hl z{?Yos=hNsh4d<`khuepJ6avlpkV;>&h4Ck6Cu{LVTgwy;<%1vlbT%>x$$nm*cmQvK zyLRIz<$^2Kdk3udy3F=E3)yG9?aZB%H4NJ1uD5#kK0hvp!~R5jMNLr5S6{5+vs5WT zXK%<|{C(Z)gAtgsyDuNQhK!p`pnEwf-JR_($`K<qxvEbuty>lf+JVoHlU71^PS`vi zg*|5arxV$UwzB=~x&L>xJ-lkS<6Pr#*0bM!mi{=P?V>;h5NByZ$lD9LSa0k0W?SQa zSvC+m6aIH(_NJVZ-im8xGn1?;#<FB3T)Gf;CR-r*xW~fw!>)fr`MBuO@@T%kMd&PP z+70m6ZA(q7tnN{98V*~`*w8wK(tA2er$dD8hqbuelA7(yvvj{s(A`~?1=d*{Iz{)@ zpW3IjU{@rn@fEgKa@rj|!`Mjm9E&7XYA<|b$yB>9d>AZ+iPB}#`PZN5xB0QC#uACy z?xk?(zJ8CF>dn}5{GMS((vtpjN5{Ut2H3bV<cJA1?U{PX$NY*FIoINxhqk81hj3Nf zV&BDXRw5o23Y^J}1wiCz8&^H3Qq;3uSSN`f(T|OdjZ)O~DFnqG&>0x-jnOg^EKH$N zp-x9fH*M=Vn$>xAb(Qp<Pg_&7-@F0F>MH@oNi~+1n#1auOSumP$}~U)D!c@`w2$Ef z(Pndti`iLOFq0uwd4qsfo8=!BF*f<C*@pH30T^~rrXM!!*xK5PwadXB<)jv?H=eCm z+3>EQ>-N7r<TEvuck8khhTX_gZxBXD7MYZ%Pfz^ayl*T=IPhNu<aKO~6Y0^`+R9s? zya>Bd3w!Dx(z)+2SgxeV>LaL?d+HH^s;nAMm^|+P5cSq!QN~-e@X!MiGk`cWj8f7a z(hMbygf!9(5-Q!@q9ENhlyrB8bfX~B-Cf^%&b{Zm`~}bOFv0KL@7`;zy_UQ%Hj2|% zB%L4|;UGYoFll08$*~|^^|IVK2i`u{A49gT%kPJIFoB$ewMAK+t5<+zJPVjl777Tx z64v%PP<T?(&6uqtP~VE7#_yBrAhEobAJd7y7D*inZBQ6VR1*@J7$UCZlum>?dGrSj zlyt|$Fdc>YZD6Ip6(o49*>p7wrXOttR<fsd3xha@0DJ0hi}<gDj?oLn-Lq0?Q<{Sy zk~Spf1l50;M+Iq~OTwoAQZWSez}4trnCAXzet)f8QXyTiYG4};hEQrPnojZ9VphS4 zPvy$W&(I)lFoBfu;dGF4OFP_Rj-3S^pU0~^;w|bw^VZ1VLS|9$l(R10`UPyR#Ix42 z#zPthX{_Q@c#LX@J>N!YUWN3GY4?3trAhJH!MjcPF^%s3`;4ouAA)@Sx`g=+*IR>C zDD4k<z`q}`xd*xbO^GLx_xDdRbn9h@mp*vScHOG5lct@Q-NvImR^Fyh4c#fZ-=5SK z8gMl|d6XPUBnbmD5mq`74PEHUMc3CecEOLK<a`aiBC?8Je;0-&Ev$6C@4Ls!et0c6 zNN7it-=#f_<R&!RCkef5>NhfMDExGK<9Yr^S9rZ>$*9Fz0~OSic6n8(o&I^IVfn?h z<lW<4+WETJ=zgWhjJpB}TlCdoNxlvt-f(*cZ^`LA@uA^$n&sBR8K$zA$l~XE&aIZy z89}V`C=?K1#qD9Wj@#YrOON2D!#pLi7J-WxmltKWTtjyU-uo*nEgxJJ8VP77&WaSY zA0D!kMw<uy<?<Sk!^)hfr4Jbi9$s!^&FuJZPI^|~L>&k{4DP+a_b?7s<EQ%CbTB1L z@BPP$_~Ol%^WGP(F{q&N@=)CE`<}ERG1q7m#MSQ9Q(|T+{+`hTxgD3uFWa%PLzi;{ zv<KE~&g+IV0`Hp$(~5=96My~$hFV#-8(;~)al1N9YSMCV_g3*bJ=l}!ofXb{(Gzl% z*<^g0`^<(*=5F~V!;0sLAw5XkB=by}`uN%fE4`r^1JIH0-;Lc*4J*_Hx38GYK) zdBcG=*6My*xJAi(Y*_UBY^*}1-Suf{<$me8Z;Ft^wzVFj>dfJV=+)}X0bmW3YoOx3 zTXx=?q~GS-o=HxlHsQVXuKB%5=QDN|QlG=sbh9U7D@u^u&FZ_D_TBVG2CAhb6u<Wu zk4g!E63bkKUI<U*%dU3<(z@o~q#l9TW)){^1$Q%*NB`ZJO2+j7xgJ~Fb(Prf>Z*)y z=krfZb4G%e9d(|IFt3y0iU=`-kF#Oqv-wtP{mb2BNAtUp@k6M17rLjr=Kc2<scygW z8ulDymmkJyrC)v8_I6zUcyf~zvs+D4@=3ℑ5u8jxy-0ufSf5XrZFuTKL3KhGs8j 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μ7aPg*pnHRFh>MuJdc=H?q7YPkM>cu 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ตVDHdt?)^Uoo;6vOUMS`>jYKikyj%m=}+1 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 literal 0 HcmV?d00001 diff --git a/ui/.gitignore b/ui/.gitignore new file mode 100644 index 0000000..a244087 --- /dev/null +++ b/ui/.gitignore @@ -0,0 +1,5 @@ +*.pyc +build +dist +*.egg-info +__pycache__ diff --git a/ui/LICENSE b/ui/LICENSE new file mode 100644 index 0000000..0b86ea0 --- /dev/null +++ b/ui/LICENSE @@ -0,0 +1,28 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Source: https://github.com/gustavo-iniguez-goya/opensnitch +Upstream-Name: python3-opensnitch-ui +Files: * +Copyright: + 2017-2018 evilsocket + 2019-2020 Gustavo Iñiguez Goia +Comment: Debian packaging is licensed under the same terms as upstream +License: GPL-3.0 + This program is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later + version. + . + This program is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the GNU General Public License for more + details. + . + You should have received a copy of the GNU General Public + License along with this program. If not, If not, see + http://www.gnu.org/licenses/. + . + On Debian systems, the full text of the GNU General Public + License version 3 can be found in the file + '/usr/share/common-licenses/GPL-3'. diff --git a/ui/MANIFEST.in b/ui/MANIFEST.in new file mode 100644 index 0000000..932b95e --- /dev/null +++ b/ui/MANIFEST.in @@ -0,0 +1,3 @@ +recursive-include opensnitch/res * +recursive-include opensnitch/i18n *.qm +include LICENSE diff --git a/ui/Makefile b/ui/Makefile new file mode 100644 index 0000000..f994ac7 --- /dev/null +++ b/ui/Makefile @@ -0,0 +1,17 @@ +all: opensnitch/resources_rc.py + +install: + +opensnitch/resources_rc.py: translations # deps + @pyrcc5 -o opensnitch/resources_rc.py opensnitch/res/resources.qrc + sed -i 's/^import ui_pb2/from . import ui_pb2/' opensnitch/ui_pb2* + +translations: + @$(MAKE) -C i18n + +deps: + @pip3 install -r requirements.txt + +clean: + @rm -rf *.pyc + @rm -rf opensnitch/resources_rc.py diff --git a/ui/bin/opensnitch-ui b/ui/bin/opensnitch-ui new file mode 100755 index 0000000..97e15ed --- /dev/null +++ b/ui/bin/opensnitch-ui @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 + +from PyQt5 import QtWidgets, QtGui, QtCore + +import sys +import os +import time +import signal +import argparse +import logging +from threading import Timer + +logging.getLogger().disabled = True + +from concurrent import futures + +import grpc + +dist_path = '/usr/lib/python3/dist-packages/' +if dist_path not in sys.path: + sys.path.append(dist_path) + +from opensnitch.service import UIService +from opensnitch.config import Config +from opensnitch.utils import Themes +import opensnitch.version +import opensnitch.ui_pb2 +from opensnitch.ui_pb2_grpc import add_UIServicer_to_server + +def on_exit(): + server.stop(0) + app.quit() + sys.exit(0) + +def supported_qt_version(major, medium, minor): + q = QtCore.QT_VERSION_STR.split(".") + return int(q[0]) >= major and int(q[1]) >= medium and int(q[2]) >= minor + +def load_translations(): + locale = QtCore.QLocale.system() + i18n_path = os.path.dirname(os.path.realpath(opensnitch.__file__)) + "/i18n" + print("Loading translations:", i18n_path, "locale:", locale.name()) + translator = QtCore.QTranslator() + translator.load(i18n_path + "/" + locale.name() + "/opensnitch-" + locale.name() + ".qm") + + return translator + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='OpenSnitch UI service.') + parser.add_argument("--socket", dest="socket", default="unix:///tmp/osui.sock", help="Path of the unix socket for the gRPC service (https://github.com/grpc/grpc/blob/master/doc/naming.md).", metavar="FILE") + parser.add_argument("--max-clients", dest="serverWorkers", default=10, help="Max number of allowed clients (incoming connections).") + + args = parser.parse_args() + + os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1" + if supported_qt_version(5,6,0): + try: + # NOTE: maybe we also need Qt::AA_UseHighDpiPixmaps + QtCore.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True) + except Exception: + pass + + translator = load_translations() + app = QtWidgets.QApplication(sys.argv) + app.installTranslator(translator) + thm = Themes.instance() + thm.load_theme(app) + + service = UIService(app, on_exit) + # @doc: https://grpc.github.io/grpc/python/grpc.html#server-object + server = grpc.server(futures.ThreadPoolExecutor(), + options=( + # https://github.com/grpc/grpc/blob/master/doc/keepalive.md + # https://grpc.github.io/grpc/core/group__grpc__arg__keys.html + # send keepalive ping every 5 second, default is 2 hours) + ('grpc.keepalive_time_ms', 5000), + # after 5s of inactivity, wait 20s and close the connection if + # there's no response. + ('grpc.keepalive_timeout_ms', 20000), + ('grpc.keepalive_permit_without_calls', True), + )) + + add_UIServicer_to_server(service, server) + + if args.socket.startswith("unix://"): + socket = args.socket[7:] + socket = os.path.abspath(socket) + server.add_insecure_port("unix:%s" % socket) + else: + server.add_insecure_port(args.socket) + + # https://stackoverflow.com/questions/5160577/ctrl-c-doesnt-work-with-pyqt + signal.signal(signal.SIGINT, signal.SIG_DFL) + + try: + # print "OpenSnitch UI service running on %s ..." % socket + server.start() + app.exec_() + except KeyboardInterrupt: + on_exit() + diff --git a/ui/i18n/Makefile b/ui/i18n/Makefile new file mode 100644 index 0000000..2706005 --- /dev/null +++ b/ui/i18n/Makefile @@ -0,0 +1,37 @@ +SOURCES += ../opensnitch/service.py \ + ../opensnitch/dialogs/prompt.py \ + ../opensnitch/dialogs/preferences.py \ + ../opensnitch/dialogs/ruleseditor.py \ + ../opensnitch/dialogs/processdetails.py \ + ../opensnitch/dialogs/stats.py + +FORMS += ../opensnitch/res/prompt.ui \ + ../opensnitch/res/ruleseditor.ui \ + ../opensnitch/res/preferences.ui \ + ../opensnitch/res/process_details.ui \ + ../opensnitch/res/stats.ui + +#TSFILES contains all *.ts files in locales/ and its subfolders +TSFILES := $(shell find locales/ -type f -name '*.ts') +#QMFILES contains all *.qm files in locales/ and its subfolders +QMFILES := $(shell find locales/ -type f -name '*.qm') +#if QMFILES is empty, we set it to phony target to run unconditionally +ifeq ($(QMFILES),) +QMFILES := "qmfiles" +endif + +all: $(TSFILES) $(QMFILES) + +#if any file from SOURCES or FORMS is older than any file from $(TSFILES) +#or if opensnitch_i18n.pro was manually modified +$(TSFILES): $(SOURCES) $(FORMS) opensnitch_i18n.pro + @pylupdate5 opensnitch_i18n.pro + +#if any of the *.ts files are older that any of the *.qm files +#QMFILES may also be a phony target (when no *.qm exist yet) which will always run +$(QMFILES):$(TSFILES) + @./generate_i18n.sh + for lang in $$(ls locales/); do \ + if [ ! -d ../opensnitch/i18n/$$lang ]; then mkdir -p ../opensnitch/i18n/$$lang ; fi ; \ + cp locales/$$lang/opensnitch-$$lang.qm ../opensnitch/i18n/$$lang/ ; \ + done diff --git a/ui/i18n/README.md b/ui/i18n/README.md new file mode 100644 index 0000000..c10f80d --- /dev/null +++ b/ui/i18n/README.md @@ -0,0 +1,37 @@ + +### Adding a new translation: +0. Install needed packages: `apt install qtchooser pyqt5-dev-tools` +1. mkdir `locales/<YOUR LOCALE>/` + (echo $LANG) +2. add the path to opensnitch_i18n.pro: +``` + TRANSLATIONS += locales/es_ES/opensnitch-es_ES.ts \ + locales/<YOUR LOCALE>/opensnitch-<YOUR LOCALE>.ts +``` +3. make + +### Updating translations: + +1. update translations definitions: + - pylupdate5 opensnitch_i18n.pro + +2. translate a language: + - linguist locales/es_ES/opensnitch-es_ES.ts + +3. create .qm file: + - lrelease locales/es_ES/opensnitch-es_ES.ts -qm locales/es_ES/opensnitch-es_ES.qm + +or: + +1. make +2. linguist locales/es_ES/opensnitch-es_ES.ts +3. make + +### Installing translations (manually) + +In order to test a new translation: + +`mkdir -p /usr/lib/python3/dist-packages/opensnitch/i18n/<YOUR LOCALE>/` +`cp locales/<YOUR LOCALE>/opensnitch-<YOUR LOCALE>.qm /usr/lib/python3/dist-packages/opensnitch/i18n/<YOUR LOCALE>/` + +Note: the destination path may vary depending on your system. diff --git a/ui/i18n/generate_i18n.sh b/ui/i18n/generate_i18n.sh new file mode 100755 index 0000000..b85fb65 --- /dev/null +++ b/ui/i18n/generate_i18n.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +app_name="opensnitch" +langs_dir="./locales" +lrelease="lrelease" +if ! command -v $lrelease; then + # fedora + lrelease="lrelease-qt5" +fi + +#pylupdate5 opensnitch_i18n.pro + +for lang in $(ls $langs_dir) +do + lang_path="$langs_dir/$lang/$app_name-$lang" + $lrelease $lang_path.ts -qm $lang_path.qm +done diff --git a/ui/i18n/locales/de_DE/opensnitch-de_DE.ts b/ui/i18n/locales/de_DE/opensnitch-de_DE.ts new file mode 100644 index 0000000..1f23f5e --- /dev/null +++ b/ui/i18n/locales/de_DE/opensnitch-de_DE.ts @@ -0,0 +1,2384 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.0" language="de_DE" sourcelanguage=""> +<context> + <name>Dialog</name> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="34"/> + <source>opensnitch-qt</source> + <translation>OpenSnitch Firewall</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="299"/> + <source>User ID</source> + <translation>User ID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="333"/> + <source><html><head/><body><p><span style=" font-weight:600;">Executed from</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-weight:600;">Ausgeführt von</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="630"/> + <source>TextLabel</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="426"/> + <source>Source IP</source> + <translation>Quell-IP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="449"/> + <source>Process ID</source> + <translation>Prozess ID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="582"/> + <source>Destination IP</source> + <translation>Ziel-IP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="605"/> + <source>Dst Port</source> + <translation>Zielport</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="150"/> + <source>Chromium Web Browser</source> + <translation type="obsolete">Chromium-Webbrowser</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="226"/> + <source>(/path/to/bin/chromium)</source> + <translation type="obsolete">(Pfad/zur/bin/chromium)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="271"/> + <source>Chromium Web Browser wants to connect to www.evilsocket.net on tcp port 443. And maybe to www.goodsocket.net on port 344</source> + <translation type="obsolete">Der Chromium-Webbrowser möchte eine Verbindung zu www.evilsocket.net über TCP-Port 443 herstellen. Und möglicherweise zu www.goodsocket.net über Port 344</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="679"/> + <source>from this executable</source> + <translation>von dieser ausführbaren Datei</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="684"/> + <source>from this command line</source> + <translation>von dieser Kommandozeile</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="689"/> + <source>this destination port</source> + <translation>dieser Zielport</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="694"/> + <source>this user</source> + <translation>dieser Benutzer</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="699"/> + <source>this destination ip</source> + <translation>diese Ziel-IP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="728"/> + <source>once</source> + <translation>einmal</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="733"/> + <source>30s</source> + <translation>30s</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="738"/> + <source>5m</source> + <translation>5m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="743"/> + <source>15m</source> + <translation>15m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="748"/> + <source>30m</source> + <translation>30m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="753"/> + <source>1h</source> + <translation>1h</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="706"/> + <source>for this session</source> + <translation type="obsolete">für diese Sitzung</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="763"/> + <source>forever</source> + <translation>für immer</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="789"/> + <source>Deny</source> + <translation>Verweigern</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="818"/> + <source>Allow</source> + <translation>Erlauben</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="847"/> + <source>+</source> + <translation>+</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="758"/> + <source>until reboot</source> + <translation>bis zum Neustart</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="704"/> + <source>from this PID</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>New node connected</name> +</context> +<context> + <name>PreferencesDialog</name> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="14"/> + <source>Preferences</source> + <translation>Einstellungen</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="472"/> + <source>UI</source> + <translation>Popup-Fenster</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="54"/> + <source><html><head/><body><p>This timeout is the countdown you see when a pop-up dialog is shown.</p></body></html></source> + <translation type="obsolete"><html><head/><body><p>Dieses Zeitlimit ist der Countdown, der angezeigt wird, wenn ein Popup-Fenster angezeigt wird.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="454"/> + <source>Default timeout</source> + <translation>Standardzeitlimit</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="331"/> + <source>Pop-up default duration</source> + <translation>Popup-Standarddauer</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="754"/> + <source>Default duration</source> + <translation>Standarddauer</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="162"/> + <source>Pop-up default action</source> + <translation type="obsolete">Popup-Standardaktion</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="483"/> + <source>Default action</source> + <translation type="obsolete">Standardaktion</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="314"/> + <source>Default target</source> + <translation>Standardfilterung</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="170"/> + <source>center</source> + <translation>mittig</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="175"/> + <source>top right</source> + <translation>oben rechts</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="180"/> + <source>bottom right</source> + <translation>unten rechts</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="185"/> + <source>top left</source> + <translation>oben links</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="190"/> + <source>bottom left</source> + <translation>unten links</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="167"/> + <source>Prompt dialog default position on screen</source> + <translation type="obsolete">Grundposition</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="274"/> + <source>by executable</source> + <translation>nach ausführbarer Datei</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="279"/> + <source>by command line</source> + <translation>nach Befehl</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="284"/> + <source>by destination port</source> + <translation>nach Zielport</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="289"/> + <source>by destination ip</source> + <translation>nach Ziel-IP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="294"/> + <source>by user id</source> + <translation>nach UID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="863"/> + <source>once</source> + <translation>einmal</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="210"/> + <source>30s</source> + <translation>30s</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="215"/> + <source>5m</source> + <translation>5m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="220"/> + <source>15m</source> + <translation>15m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="225"/> + <source>30m</source> + <translation>30m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="230"/> + <source>1h</source> + <translation>1h</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="240"/> + <source>for this session</source> + <translation type="obsolete">für diese Sitzung</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="240"/> + <source>forever</source> + <translation>für immer</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="905"/> + <source>deny</source> + <translation>verweigern</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="914"/> + <source>allow</source> + <translation>erlauben</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="406"/> + <source>Disable pop-ups, only display an alert</source> + <translation type="obsolete">Popups deaktivieren, nur eine Warnung anzeigen</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="711"/> + <source>Nodes</source> + <translation>Knoten</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="717"/> + <source>Process monitor method</source> + <translation>Prozessüberwachungsmethode</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="751"/> + <source><html><head/><body><p>The default duration will take place when there's no UI connected.</p></body></html></source> + <translation><html><head/><body><p>Die Standarddauer gilt, wenn keine Benutzeroberfläche verbunden ist.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="881"/> + <source><html><head/><body><p>Address of the node.</p><p>Default: unix:///tmp/osui.sock (unix:// is mandatory if it's a Unix socket)</p><p>It can also be an IP address with the port: 127.0.0.1:50051</p></body></html></source> + <translation><html><head/><body><p>Knotenadresse.</p><p>Standardmäßig: unix: ///tmp/osui.sock (unix: // ist erforderlich, wenn ein Unix-Socket vorhanden ist)</p><p>Es kann sich auch um eine IP mit diesem Format handeln: 127.0.0.1:50051, 192.168.1.122:12345 usw.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="884"/> + <source>Address</source> + <translation>Adresse</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1024"/> + <source>Default log level</source> + <translation>Standardprotokollstufe</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="932"/> + <source>Version</source> + <translation>Version</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="790"/> + <source><html><head/><body><p>The default action will take place when there's no UI connected.</p></body></html></source> + <translation><html><head/><body><p>Die Standardaktion wird angewendet, wenn keine Benutzeroberfläche verbunden ist.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="734"/> + <source><html><head/><body><p>Log file to write logs.<br/></p><p>/dev/stdout will print logs to the standard output.</p></body></html></source> + <translation><html><head/><body><p>Protokolldatei, in welche die Protokolle geschrieben werden sollen.<br/></p><p>/dev/stdout schreibt die Protokolle in die Standardausgabe des Dienstes..</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="737"/> + <source>Log file</source> + <translation>Logdatei</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="578"/> + <source><html><head/><body><p>If checked, opensnitch will prompt you to allow or deny connections that don't have an asocciated PID, due to several reasons.</p><p>The pop-up dialog will only contain information about the network connection.</p></body></html></source> + <translation type="obsolete">Wenn Sie diese Option aktivieren, werden Sie von OpenSnitch aufgefordert, Verbindungen zu akzeptieren oder zu verweigern, denen aus verschiedenen Gründen keine PID zugeordnet ist. + +Das Popup-Fenster enthält nur Informationen zur Verbindung. + +Hinweis: Diese Verbindungen müssen nicht darauf hinweisen, dass etwas Verdächtiges passiert. Einfach +ist, dass wir die PID nicht entdeckt haben (zum Beispiel Verbindungen, die nicht vom Computer stammen, oder fehlerhafte Pakete).</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="581"/> + <source>Intercept Unknown Connections</source> + <translation type="obsolete">Unbekannte Verbindungen abfangen</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="809"/> + <source>HostName</source> + <translation>HostName</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="983"/> + <source>unix:///tmp/osui.sock</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="868"/> + <source>until restart</source> + <translation>bis zum Neustart</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="873"/> + <source>always</source> + <translation>immer</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="995"/> + <source>/var/log/opensnitchd.log</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1000"/> + <source>/dev/stdout</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="767"/> + <source>Apply configuration to all nodes</source> + <translation>Konfiguration auf alle Knoten anwenden</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1039"/> + <source>Database</source> + <translation>Datenbank</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="630"/> + <source>Database name</source> + <translation type="obsolete">Datenbankname</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1074"/> + <source>In memory</source> + <translation>Im Speicher</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1079"/> + <source>File</source> + <translation>Datei</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="669"/> + <source>/path/to/the/file.db</source> + <translation type="obsolete">/Pfad/zu/der/Datei.db</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1345"/> + <source>Close</source> + <translation>Schließen</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1356"/> + <source>Apply</source> + <translation>Anwenden</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1367"/> + <source>Save</source> + <translation>Speichern</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="235"/> + <source>until reboot</source> + <translation>Bis zum Neustart</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="347"/> + <source>The advanced view allows you to easily select multiple fields to filter connections</source> + <translation>In der erweiterten Ansicht können Sie ganz einfach mehrere Felder auswählen, um Verbindungen zu filtern</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="350"/> + <source>Show advanced view by default</source> + <translation>Standardmäßig erweiterte Ansicht anzeigen</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="665"/> + <source>Action</source> + <translation>Aktion</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="366"/> + <source><html><head/><body><p>If checked, the pop-ups will be displayed with the advanced view active.</p></body></html></source> + <translation><html><head/><body><p>Wenn diese Option aktiviert ist, werden die Pop-ups mit aktiver erweiterter Ansicht angezeigt.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="334"/> + <source>Duration</source> + <translation>Dauer</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="254"/> + <source><html><head/><body><p>By default when a new pop-up appears, in its simplest form, you'll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).</p><p>With these options, you can choose multiple fields to filter connections for.</p></body></html></source> + <translation><html><head/><body><p>Wenn ein neues Popup-Fenster in seiner einfachsten Form angezeigt wird, können Sie standardmäßig Verbindungen oder Anwendungen nach einer Eigenschaft der Verbindung (ausführbare Datei, Port, IP usw.) filtern.</p><p>Mit diesen Optionen können Sie mehrere Felder auswählen, nach denen Verbindungen gefiltert werden sollen.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="257"/> + <source>Filter connections also by:</source> + <translation>Verbindungen auch filtern nach:</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="110"/> + <source>If checked, this field will be selected when a pop-up is displayed</source> + <translation>Wenn aktiviert, wird dieses Feld ausgewählt, wenn ein Popup angezeigt wird</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="81"/> + <source>User ID</source> + <translation>User ID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="97"/> + <source>Destination port</source> + <translation>Ziel Port</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="113"/> + <source>Destination IP</source> + <translation>Ziel-IP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="451"/> + <source><html><head/><body><p>This timeout is the countdown you see when a pop-up dialog is shown.</p><p>If the pop-up is not answered, the default options will be applied.</p></body></html></source> + <translation><html><head/><body><p>Dieses Timeout ist der Countdown, den Sie sehen, wenn ein Popup-Dialogfeld angezeigt wird.</p><p>Wenn das Popup nicht beantwortet wird, werden die Standardoptionen angewendet.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1093"/> + <source>Database type</source> + <translation>Datenbanktyp</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1100"/> + <source>Select</source> + <translation>Auswählen</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="150"/> + <source><html><head/><body><p>Pop-up default action.</p><p>When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.</p><p><br/></p><p>While a pop-up is asking the user to allow or deny a connection:</p><p>1. new outgoing connections are denied.</p><p>2. known connections are allowed or denied based on the rules defined by the user.</p></body></html></source> + <translation><html><head/><body><p>Popup-Standardaktion.</p><p>Wenn eine neue ausgehende Verbindung hergestellt werden soll, wird diese Aktion standardmäßig ausgewählt. Wenn das Timeout auftritt, wird diese Option angewendet.</p><p><br/></p><p>Während ein Pop-up den Benutzer auffordert, eine Verbindung zuzulassen oder abzulehnen:</p><p>1. neue ausgehende Verbindungen werden verweigert.</p><p>2. bekannte Verbindungen werden nach den vom Benutzer definierten Regeln zugelassen oder verweigert.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="793"/> + <source>Default action when the GUI is disconnected</source> + <translation>Standardaktion, wenn die GUI getrennt ist</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="894"/> + <source>Debug invalid connections</source> + <translation>Debugge ungültige Verbindungen</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="39"/> + <source>Pop-ups</source> + <translation>Pop-ups</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="64"/> + <source>Default options</source> + <translation>Standardoptionen</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="321"/> + <source>Default position on screen</source> + <translation>Standardposition auf dem Bildschirm</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="479"/> + <source>any temporary rules</source> + <translation>jede temporäre Regel</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="492"/> + <source><html><head/><body><p>When this option is selected, the rules of the selected duration won't be added to the list of temporary rules in the GUI.</p><p><br/></p><p>Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</p></body></html></source> + <translation><html><head/><body><p>Wenn diese Option ausgewählt ist, werden die Regeln der ausgewählten Dauer nicht zur Liste der temporären Regeln in der GUI hinzugefügt.</p><p><br/></p><p>Temporäre Regeln sind weiterhin gültig und Sie können sie verwenden, wenn Sie dazu aufgefordert werden, eine neue Verbindung zuzulassen/zu verweigern.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="495"/> + <source>Don't save rules of duration</source> + <translation>Speichern Sie keine Regeln der Dauer</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="601"/> + <source>Time</source> + <translation>Zeit</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="681"/> + <source>Destination</source> + <translation>Ziel</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="649"/> + <source>Protocol</source> + <translation>Protokoll</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="697"/> + <source>Process</source> + <translation>Prozess</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="617"/> + <source>Rule</source> + <translation>Regel</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="633"/> + <source>Node</source> + <translation>Knoten</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="723"/> + <source><html><head/><body><p>If checked, opensnitch will prompt you to allow or deny connections that don't have an asocciated PID, due to several reasons, mostly due to bad state connections.</p><p>The pop-up dialog will only contain information about the network connection.</p><p>There're some scenarios where these are valid connections though, like when establishing a VPN using wireguard.</p></body></html></source> + <translation type="obsolete"><html><head/><body><p>Wenn diese Option aktiviert ist, fordert Opensnitch Sie aus verschiedenen Gründen auf, Verbindungen zuzulassen oder zu verweigern, die keine zugeordnete PID haben, hauptsächlich aufgrund von Verbindungen mit schlechtem Status.</p><p>Der Popup-Dialog enthält nur Informationen über die Netzwerkverbindung.</p><p>Es gibt jedoch einige Szenarien, in denen dies gültige Verbindungen sind, z. B. beim Einrichten eines VPN mit Wireguard.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="589"/> + <source>Events tab columns</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="299"/> + <source>by PID</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="461"/> + <source>Disable pop-ups, only display an notification</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="508"/> + <source>Desktop notifications</source> + <translation>Desktop-Benachrichtigungen</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="526"/> + <source>Use system notifications</source> + <translation>Systembenachrichtigungen verwenden</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="542"/> + <source>Use Qt notifications</source> + <translation>Qt-Benachrichtigungen verwenden</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="571"/> + <source>Test</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="891"/> + <source><html><head/><body><p>If checked, OpenSnitch will prompt you to allow or deny connections that don't have an associated PID, due to several reasons, mostly due to bad state connections.</p><p>The pop-up dialog will only contain information about the network connection.</p><p>There're some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1178"/> + <source>minutes</source> + <translation>Minuten</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1204"/> + <source>Minutes between events purges</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1227"/> + <source>days</source> + <translation>Tage</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1237"/> + <source>Maximum days of events to keep</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>ProcessDetailsDialog</name> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="14"/> + <source>Process details</source> + <translation>Prozessdetails</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="61"/> + <source>loading...</source> + <translation>wird geladen...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="81"/> + <source>CWD: loading...</source> + <translation>CWD: Laden...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="93"/> + <source>mem stats: loading...</source> + <translation>Speicherstatistik: Laden ...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="121"/> + <source>Status</source> + <translation>Status</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="135"/> + <source>Open files</source> + <translation>Dateien öffnen</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="149"/> + <source>I/O Statistics</source> + <translation>I/O Statistiken</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="163"/> + <source>Memory mapped files</source> + <translation>Dateien in den Speicher geladen</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="177"/> + <source>Stack</source> + <translation>Stapel</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="191"/> + <source>Environment variables</source> + <translation>Umgebungsvariablen</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="210"/> + <source>Application pids</source> + <translation>Anwendungs-PIDs</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="240"/> + <source>Start or stop monitoring this process</source> + <translation>Starten oder beenden Sie die Überwachung dieses Prozesses</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="256"/> + <source>Close</source> + <translation>Schließen</translation> + </message> +</context> +<context> + <name>RulesDialog</name> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="14"/> + <source>Rule</source> + <translation>Regel</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="118"/> + <source>Node</source> + <translation>Knoten</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="141"/> + <source>Apply rule to all nodes</source> + <translation>Regel auf alle Knoten anwenden</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="239"/> + <source>From this command line</source> + <translation>Von dieser Kommandozeile</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="206"/> + <source>From this executable</source> + <translation>Von dieser ausführbaren Datei</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="751"/> + <source>Action</source> + <translation>Aktion</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="138"/> + <source>/path/to/executable, .*/bin/executable[0-9\.]+$, ...</source> + <translation type="obsolete">/Pfad/zur/ausführbaren/Datei, .*/bin/executable[0-9\.]+$, ...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="383"/> + <source>To this IP / Network</source> + <translation>Zu dieser IP / Netzwerk</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="792"/> + <source>once</source> + <translation>einmal</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="797"/> + <source>30s</source> + <translation>30s</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="802"/> + <source>5m</source> + <translation>5m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="807"/> + <source>15m</source> + <translation>15m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="812"/> + <source>30m</source> + <translation>30m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="817"/> + <source>1h</source> + <translation>1h</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="230"/> + <source>until restart</source> + <translation type="obsolete">bis zum Neustart</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="827"/> + <source>always</source> + <translation>immer</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="293"/> + <source>To this port</source> + <translation>Zu diesem Port</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="169"/> + <source>From this user ID</source> + <translation>Von dieser Benutzer-ID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="419"/> + <source>Commas or spaces are not allowed to specify multiple domains. + +Use regular expressions instead: +.*(opensnitch|duckduckgo).com +.*\.google.com + +or a single domain: +www.gnu.org - it'll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ... +gnu.org - it'll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source> + <translation>Kommas oder Leerzeichen dürfen nicht mehrere Domänen angeben. + +Verwenden Sie stattdessen reguläre Ausdrücke: +.*(opensnitch|duckduckgo).com +.*\.google.com + +oder eine einzelne Domain: +www.gnu.org - es wird nur mit www.gnu.org, noch ftp.gnu.org oder www2.gnu.org übereinstimmen, ... +gnu.org - es wird nur mit gnu.org, www.gnu.org oder ftp.gnu.org übereinstimmen, ...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="430"/> + <source>www.domain.org, .*\.domain.org</source> + <translation>www.domain.org, .*\.domain.org</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="323"/> + <source><html><head/><body><p>Only TCP, UDP or UDPLITE are allowed</p><p>You can use regexp, i.e.: ^(TCP|UDP)$</p></body></html></source> + <translation><html><head/><body><p>Es sind nur TCP-, UDP- oder UDPLITE-Optionen zulässig.</p><p>Sie können reguläre Ausdrücke verwenden zu diesen Optionen, zum Beispiel TCP oder UDP: ^ (TCP|UDP)$</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="333"/> + <source>TCP</source> + <translation>TCP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="338"/> + <source>UDP</source> + <translation>UDP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="343"/> + <source>UDPLITE</source> + <translation>UDPLITE</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="348"/> + <source>TCP6</source> + <translation>TCP6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="353"/> + <source>UDP6</source> + <translation>UDP6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="358"/> + <source>UDPLITE6</source> + <translation>UDPLITE6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="440"/> + <source>You can specify a single IP: +- 192.168.1.1 + +or a regular expression: +- 192\.168\.1\.[0-9]+ + +multiple IPs: +- ^(192\.168\.1\.1|172\.16\.0\.1)$ + +You can also specify a subnet: +- 192.168.1.0/24 + +Note: Commas or spaces are not allowed to separate IPs or networks.</source> + <translation>Sie können eine IP angeben: +- 192.168.1.1 + +oder ein regulärer Ausdruck: +- 192\.168\.1\.[0-9]+ + +mehrere IPs: +- ^(192\.168\.1\.1|172\.16\.0\.1)$ + +Sie können auch ein Subnetz angeben: +- 192.168.1.0/24 + +Hinweis: Kommas und Leerzeichen dürfen keine IPs oder Netzwerke angeben.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="459"/> + <source>LAN</source> + <translation>LAN</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="464"/> + <source>127.0.0.0/8</source> + <translation>127.0.0.0/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="469"/> + <source>192.168.0.0/24</source> + <translation>192.168.0.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="474"/> + <source>192.168.1.0/24</source> + <translation>192.168.1.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="479"/> + <source>192.168.2.0/24</source> + <translation>192.168.2.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="484"/> + <source>192.168.0.0/16</source> + <translation>192.168.0.0/16</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="489"/> + <source>169.254.0.0/16</source> + <translation>169.254.0.0/16</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="494"/> + <source>172.16.0.0/12</source> + <translation>172.16.0.0/12</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="499"/> + <source>10.0.0.0/8</source> + <translation>10.0.0.0/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="504"/> + <source>::1/128</source> + <translation>::1/128</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="509"/> + <source>fc00::/7</source> + <translation>fc00::/7</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="514"/> + <source>ff00::/8</source> + <translation>ff00::/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="519"/> + <source>fe80::/10</source> + <translation>fe80::/10</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="524"/> + <source>fd00::/8</source> + <translation>fd00::/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="784"/> + <source>Duration</source> + <translation>Dauer</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="376"/> + <source>Protocol</source> + <translation>Protokoll</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="300"/> + <source>To this host</source> + <translation>Zu diesem Host</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="843"/> + <source>Deny</source> + <translation>Verweigern</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="877"/> + <source>Allow</source> + <translation>Erlauben</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="899"/> + <source>Name</source> + <translation>Name</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="22"/> + <source>Enable</source> + <translation>Aktivieren</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="706"/> + <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them. + +000-allow-localhost +001-deny-broadcast +...</source> + <translation>Regeln werden in alphabetischer Reihenfolge überprüft, daher können Sie diese so benennen, um sie zu priorisieren. + +000-allow-localhost +0001-Deny-Broadcast +...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="713"/> + <source>leave blank to autocreate</source> + <translation>Lassen Sie das Feld leer, um den Namen automatisch zuzuweisen</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="46"/> + <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one. + +You must name the rule in such manner that it'll be checked first, because they're checked in alphabetical order. For example: + +[x] Priority - 000-priority-rule +[ ] Priority - 001-less-priority-rule</source> + <translation>Wenn Sie diese Option aktivieren, hat diese Regel bei der Bewertung Vorrang vor den übrigen Regeln. Danach werden keine Regeln mehr überprüft. + +Sie müssen die Regel so benennen, dass sie zuerst überprüft wird, da sie in alphabetischer Reihenfolge überprüft wird. Zum Beispiel: + +[x] Priorität - 000-Prioritätsregel +[] Priorität - 001-Regel mit weniger Priorität</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="54"/> + <source>Priority rule</source> + <translation>Prioritätsregel</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="74"/> + <source><html><head/><body><p>By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.<br/></p><p>If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.</p></body></html></source> + <translation><html><head/><body><p>Standardmäßig wird bei den Feldern einer Regel NICHT zwischen Groß- und Kleinschreibung unterschieden, d. H.; Wenn ein Prozess versucht, auf gOOgle.CoM zuzugreifen, und Sie eine Regel zum Verweigern haben. * Google.com, wird die Verbindung blockiert.<br/></p><p>Wenn Sie diese Option aktivieren und gOOgle.CoM GENAU blockieren möchten, müssen Sie dies im Regelfeld angeben, also die genaue Domain, die Sie filtern möchten (in diesem Fall: gOOgle.CoM).</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="77"/> + <source>Case-sensitive</source> + <translation>Groß- und Kleinschreibung beachten</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="369"/> + <source><html><head/><body><p>You can specify multiple ports using regular expressions:</p><p><br/></p><p>- 53, 80 or 443:</p><p>^(53|80|443)$</p><p><br/></p><p>- 53, 443 or 5551, 5552, 5553, etc:</p><p>^(53|443|555[0-9])$</p></body></html></source> + <translation><html><head/><body><p>Sie können mehrere Ports mit regulären Ausdrücken angeben:</p><p><br/></p><p>- 53, 80 oder 443: +</p><p>^ (53|80|443)$</p><p><br/></p><p>- 53, 443 oder 5551, 5552, 5553 usw.:</p><p>^ (53|443|555[0-9])$</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="822"/> + <source>until reboot</source> + <translation>Bis zum Neustart</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="583"/> + <source>To this list of domains</source> + <translation>Zu dieser Domainliste</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="539"/> + <source><html><head/><body><p>Select a directory with lists of domains to block or allow.</p><p>Put inside that directory files with any extension containing lists of domains.</p><p><br/>The format of each entry of a list is as follow (hosts format):</p><p>127.0.0.1 www.domain.com</p><p>or </p><p>0.0.0.0 www.domain.com</p></body></html></source> + <translation type="obsolete"><html><head/><body><p>Wählen Sie ein Verzeichnis mit Domänenlisten aus, die blockiert oder zugelassen werden sollen.</p><p>Legen Sie in diesem Verzeichnis Dateien mit einer beliebigen Erweiterung ab, die Listen von Domänen enthalten.</p><p><br/>Das Format jedes Eintrags einer Liste ist wie folgt (Hosts-Format):</p><p>127.0.0.1 www.domain.com</p><p>or </p><p>0.0.0.0 www.domain.com</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="163"/> + <source>Applications</source> + <translation>Anwendungen</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="216"/> + <source><html><head/><body><p>This field will only match the executable path. It is not modifiable by the user.<br/></p><p>You can use regular expressions to deny executions from /tmp for example:<br/></p><p>^/tmp/.*$</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="229"/> + <source><html><head/><body><p>This field will contain and match the command line that was executed by the user.<br/></p><p>If the user typed the command, only the command will appear:</p><p>telnet 1.2.3.4<br/></p><p>If the user typed the absolute or relative path to the command, that is what will appear:</p><p>/usr/bin/telnet 1.2.3.4</p><p>../../../usr/bin/telnet 1.2.3.4</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="246"/> + <source>From this PID</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="287"/> + <source>Network</source> + <translation>Netzwerk</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="536"/> + <source>List of domains/IPs</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="542"/> + <source>To this list of network ranges</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="549"/> + <source>To this list of IPs</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="574"/> + <source><html><head/><body><p>Select a directory with files containing list of IPs to block or allow:</p><p>1.2.3.4.5</p><p>1.2.3.4.6</p><p>.</p><p>etc.</p><p>One IP per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="608"/> + <source><html><head/><body><p>Select a directory with files containing list of network ranges to block or allow:</p><p>1.2.3.0/24</p><p>80.34.56.0/20</p><p>.</p><p>etc.<br/></p><p>One Network Range per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="636"/> + <source><html><head/><body><p>Select a directory with lists of domains to block or allow.</p><p>Put inside that directory files with any extension containing lists of domains.</p><p><br/>The format of each entry of a list is as follow (hosts format):</p><p>127.0.0.1 www.domain.com</p><p>or </p><p>0.0.0.0 www.domain.com</p><p>Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="651"/> + <source>To this list of domains +(regular expressions)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="677"/> + <source><html><head/><body><p>Select a directory with files containing regular expressions of domains to block or allow:</p><p>.*\.example\.com</p><p>You can also use a domain as is: &quot;example.com&quot; , and it'll match whatever.example.com, whatever.example.com.localdomain, etc.</p><p>One domain per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="857"/> + <source>Reject</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>StatsDialog</name> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="34"/> + <source>OpenSnitch Network Statistics</source> + <translation>OpenSnitch-Netzwerkstatistik</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="290"/> + <source>Save to CSV.</source> + <translation>Als CSV exportieren.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="300"/> + <source>Ctrl+S</source> + <translation>Strg+S</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="351"/> + <source>Create a new rule</source> + <translation>Erstellen Sie eine neue Regel</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="381"/> + <source><html><head/><body><p><span style=" font-size:11pt; font-weight:600;">hostname - 192.168.1.1</span></p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="420"/> + <source>Status</source> + <translation>Status</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1665"/> + <source>-</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="464"/> + <source>Start or Stop interception</source> + <translation>Abfangen starten oder stoppen</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="509"/> + <source>Events</source> + <translation>Ereignisse</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="94"/> + <source>Filter</source> + <translation>Filter</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="107"/> + <source>Allow</source> + <translation>Erlauben</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="116"/> + <source>Deny</source> + <translation>Verweigern</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="143"/> + <source>Ex.: firefox</source> + <translation>Beispiel: firefox</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="199"/> + <source>50</source> + <translation>50</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="204"/> + <source>100</source> + <translation>100</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="209"/> + <source>200</source> + <translation>200</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="214"/> + <source>300</source> + <translation>300</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="748"/> + <source>Nodes</source> + <translation>Knoten</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="554"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on the Addr column to view details of a node)</span></p></body></html></source> + <translation type="obsolete"><html><head/><body><p><span style=" font-size:7pt;">(Doppelklicken Sie auf die Addressenspalte, um Details eines Knotens anzuzeigen)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1569"/> + <source>Rules</source> + <translation>Regeln</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="857"/> + <source>enable</source> + <translation>aktivieren</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="671"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on the Name column to view details of a rule)</span></p></body></html></source> + <translation type="obsolete">(Doppelklicken Sie auf die Namenspalte, um Details einer Regel anzuzeigen.)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="692"/> + <source>search rule name</source> + <translation type="obsolete">Suchregelname</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="704"/> + <source>Application rules</source> + <translation>Anwendungsregeln</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="806"/> + <source>Permanent</source> + <translation>Dauerhaft</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="815"/> + <source>Temporary</source> + <translation>Temporär</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="933"/> + <source>Hosts</source> + <translation>Hosts</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1364"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click to view details of an item)</span></p></body></html></source> + <translation type="obsolete"><html><head/><body><p><span style=" font-size:7pt;">(Doppelklicken Sie auf eine Element, um Details anzuzeigen.)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1020"/> + <source>Applications</source> + <translation>Anwendungen</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1127"/> + <source>Addresses</source> + <translation>Adressen</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1214"/> + <source>Ports</source> + <translation>Ports</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1298"/> + <source>Users</source> + <translation>Benutzer</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1404"/> + <source>Connections</source> + <translation>Verbindungen</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1459"/> + <source>Dropped</source> + <translation>Abgelehnt</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1514"/> + <source>Uptime</source> + <translation>Betriebszeit</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1639"/> + <source>Version</source> + <translation>Version</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="227"/> + <source>Delete all intercepted events</source> + <translation>Löschen Sie alle abgefangenen Ereignisse</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="864"/> + <source>Edit rule</source> + <translation>Regel bearbeiten</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="878"/> + <source>Delete rule</source> + <translation>Regel löschen</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="926"/> + <source>Delete all intercepted hosts</source> + <translation type="obsolete">Löschen Sie alle abgefangenen Hosts</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1051"/> + <source>Delete all intercepted applications</source> + <translation type="obsolete">Löschen Sie alle abgefangenen Anwendungen</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1159"/> + <source>Delete all intercepted addresses</source> + <translation type="obsolete">Löschen Sie alle abgefangenen Adressen</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1261"/> + <source>Delete all intercepted ports</source> + <translation type="obsolete">Löschen Sie alle abgefangenen Ports</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1371"/> + <source>Delete all intercepted users</source> + <translation type="obsolete">Löschen Sie alle abgefangenen Benutzer</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="699"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on a row to view details of a rule)</span></p></body></html></source> + <translation type="obsolete"><html><head/><body><p><span style=" font-size:7pt;">(Doppelklicken Sie auf eine Zeile, um Details zu einer Regel anzuzeigen)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="912"/> + <source>Delete connections that matched this rule</source> + <translation>Verbindungen löschen, die dieser Regel entsprechen</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="797"/> + <source>All applications</source> + <translation>Alle Anwendungen</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="125"/> + <source>Reject</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="177"/> + <source>0</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>contextual_menu</name> + <message> + <location filename="../../../opensnitch/service.py" line="43"/> + <source>Statistics</source> + <translation>Statistiken</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="46"/> + <source>Help</source> + <translation>Hilfe</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="47"/> + <source>Close</source> + <translation>Schließen</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="44"/> + <source>Enable</source> + <translation>Aktivieren</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="45"/> + <source>Disable</source> + <translation>Deaktivieren</translation> + </message> +</context> +<context> + <name>menu_close</name> + <message> + <location filename="../../../opensnitch/service.py" line="131"/> + <source>Close</source> + <translation type="obsolete">Cerrar</translation> + </message> +</context> +<context> + <name>menu_help</name> + <message> + <location filename="../../../opensnitch/service.py" line="126"/> + <source>Help</source> + <translation type="obsolete">Ayuda</translation> + </message> +</context> +<context> + <name>menu_statistics</name> + <message> + <location filename="../../../opensnitch/service.py" line="120"/> + <source>Statistics</source> + <translation type="obsolete">Eventos</translation> + </message> +</context> +<context> + <name>notifications</name> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="518"/> + <source>System notifications are not available, you need to install python3-notify2.</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>popups</name> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="91"/> + <source>Allow</source> + <translation>Erlauben</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="92"/> + <source>Deny</source> + <translation>Verweigern</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="54"/> + <source>forever</source> + <translation>für immer</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="285"/> + <source>Outgoing connection</source> + <translation>Ausgehende Verbindung</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="290"/> + <source>Process launched from:</source> + <translation>Prozess ausgeführt von:</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="325"/> + <source>from this command line</source> + <translation>von dieser Kommandozeile</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="321"/> + <source>from this executable</source> + <translation>von dieser ausführbaren Datei</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="208"/> + <source>Unknown process</source> + <translation type="obsolete">Unbekannter Prozess</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="52"/> + <source>until reboot</source> + <translation>Bis zum Neustart</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="327"/> + <source>to port {0}</source> + <translation>zum Port {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="222"/> + <source><b>%s</b> is connecting to <b>%s</b> on %s port %d</source> + <translation type="obsolete"><b>%s</b> stellt eine Verbindung zu <b>%s</b> an Port%s %d her</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="228"/> + <source><b>Remote</b> process <b>%s</b> running on <b>%s</b> is connecting to <b>%s</b> on %s port %d</source> + <translation type="obsolete"><b>Remote-Prozess <b>%s</b>, der auf <b>%s</b> ausgeführt wird, stellt eine Verbindung zu <b>%s</b> auf %s Port %d her</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="390"/> + <source>to {0}</source> + <translation>zu {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="330"/> + <source>from user {0}</source> + <translation>UID {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="347"/> + <source>to {0}.*</source> + <translation>zu {0}.*</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="400"/> + <source>to *.{0}</source> + <translation>zu *.{0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="378"/> + <source>to *{0}</source> + <translation type="obsolete">zu *{0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="434"/> + <source><b>Remote</b> process %s running on <b>%s</b></source> + <translation><b>Remote-Prozess </b> %s wird ausgeführt auf <b>%s</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="438"/> + <source>is connecting to <b>%s</b> on %s port %d</source> + <translation>stellt eine Verbindung zu <b>%s</b> auf %s Port %d her</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="444"/> + <source>is attempting to resolve <b>%s</b> via %s, %s port %d</source> + <translation>versucht <b>%s</b> über%s,%s Port%d aufzulösen</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="334"/> + <source>from this PID</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/notifications.py" line="105"/> + <source>New outgoing connection</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>popups2</name> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="254"/> + <source><b>Remote</b> process <b>%s</b> running on <b>%s</b> is connecting to <b>%s</b> on %s port %d</source> + <translation type="obsolete">El proceso <b>remoto %s</b> ejecutándose en <b>%s</b> está conectándose a <b>%s</b> en el puerto %s %d</translation> + </message> +</context> +<context> + <name>preferences</name> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="171"/> + <source>Exception saving config: %s</source> + <translation type="obsolete">Fehler beim Speichern der Konfiguration: %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="177"/> + <source>Applying configuration on %s ...</source> + <translation type="obsolete">Konfiguration in %s anwenden ...</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="208"/> + <source>Server address can not be empty</source> + <translation>Die Serveradresse darf nicht leer sein</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="227"/> + <source>Error loading %s configuration</source> + <translation type="obsolete">Fehler beim Laden der Konfiguration %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="448"/> + <source>Configuration applied.</source> + <translation>Konfiguration angewendet.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="257"/> + <source>Error applying configuration: %s</source> + <translation type="obsolete">Fehler beim Anwenden der Konfiguration: %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="299"/> + <source>Exception saving config: {0}</source> + <translation>Fehler beim Speichern der Konfiguration: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="389"/> + <source>Applying configuration on {0} ...</source> + <translation>Konfiguration in {0} anwenden ...</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="238"/> + <source>Error loading {0} configuration</source> + <translation>Fehler beim Laden der Konfiguration {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="450"/> + <source>Error applying configuration: {0}</source> + <translation>Fehler beim Anwenden der Konfiguration: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="323"/> + <source>Warning</source> + <translation>Warnung</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="323"/> + <source>You must select a file for the database<br>or choose "In memory" type.</source> + <translation>Sie müssen eine Datei für die Datenbank auswählen<br>oder wählen Sie den Typ "Im Speicher".</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="329"/> + <source>DB type changed</source> + <translation>DB-Typ geändert</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="329"/> + <source>Restart the GUI in order effects to take effect</source> + <translation>Starten Sie die GUI neu, damit die Effekte wirksam werden</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="480"/> + <source>Hover the mouse over the texts to display the help<br><br>Don't forget to visit the wiki: <a href="{0}">{0}</a></source> + <translation>Fahren Sie mit der Maus über die Texte, um die Hilfe anzuzeigen<br><br>Vergessen Sie nicht, das Wiki zu besuchen: <a href="{0}">{0}</a></translation> + </message> +</context> +<context> + <name>proc_details</name> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="97"/> + <source><b>Error loading process information:</b> <br><br> + +</source> + <translation><b>Fehler beim Laden der Prozessinformationen:</b> <br><br> + +</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="116"/> + <source><b>Error stopping monitoring process:</b><br><br></source> + <translation><b>Fehler beim Beenden des Überwachungsprozesses:</b><br><br></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="156"/> + <source>loading...</source> + <translation>Wird geladen...</translation> + </message> +</context> +<context> + <name>rules</name> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="162"/> + <source>There're no nodes connected.</source> + <translation>Es sind keine Knoten verbunden.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="203"/> + <source>Rule applied.</source> + <translation>Regel angewendet.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="123"/> + <source>Error applying rule: %s</source> + <translation type="obsolete">Fehler beim Anwenden der Regel:%s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="527"/> + <source>protocol can not be empty, or uncheck it</source> + <translation>Das Protokoll darf nicht leer sein oder die Option deaktivieren</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="541"/> + <source>Protocol regexp error</source> + <translation>Protokoll-Regexp-Fehler</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="545"/> + <source>process path can not be empty</source> + <translation>Prozesspfad darf nicht leer sein</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="559"/> + <source>Process path regexp error</source> + <translation>Prozesspfad-Regexp-Fehler</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="563"/> + <source>command line can not be empty</source> + <translation>Befehlszeile darf nicht leer sein oder die Option deaktivieren</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="577"/> + <source>Command line regexp error</source> + <translation>Befehlszeilen-Regexp-Fehler</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="581"/> + <source>Dest port can not be empty</source> + <translation>Der Zielport darf nicht leer sein oder die Option deaktivieren</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="595"/> + <source>Dst port regexp error</source> + <translation>Fehler im regulären Ausdruck des Zielports</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="599"/> + <source>Dest host can not be empty</source> + <translation>Der Zielhost kann nicht leer sein oder die Option deaktivieren</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="613"/> + <source>Dst host regexp error</source> + <translation>Fehler beim regulären Ausdruck des Zielhosts</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="617"/> + <source>Dest IP/Network can not be empty</source> + <translation>Ziel-IP / Netzwerk darf nicht leer sein</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="639"/> + <source>Dst IP regexp error</source> + <translation>Fehler beim regulären Ausdruck der Ziel-IP</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="651"/> + <source>User ID can not be empty</source> + <translation>Die Benutzer-ID darf nicht leer sein oder die Option deaktivieren</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="665"/> + <source>User ID regexp error</source> + <translation>Regexp-Fehler der Benutzer-ID</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="205"/> + <source>Error applying rule: {0}</source> + <translation>Fehler beim Anwenden der Regel: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="429"/> + <source><b>Error loading rule</b></source> + <translation><b>Fehler beim Laden der Regel</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="739"/> + <source>Lists field cannot be empty</source> + <translation>Listenfeld darf nicht leer sein</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="741"/> + <source>Lists field must be a directory</source> + <translation>Listenfeld muss ein Verzeichnis sein</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="777"/> + <source><b>Rule not supported</b></source> + <translation><b>Regel nicht unterstützt</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="179"/> + <source>There's already a rule with this name.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="669"/> + <source>PID field can not be empty</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="683"/> + <source>PID field regexp error</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="764"/> + <source>Select at least one field.</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>stats</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="310"/> + <source>Not running</source> + <translation>Gestoppt</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="311"/> + <source>Disabled</source> + <translation>Deaktiviert</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="312"/> + <source>Running</source> + <translation>Eingeschaltet</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="412"/> + <source>OpenSnitch Network Statistics</source> + <translation type="obsolete">Eventos de OpenSnitch</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="414"/> + <source>OpenSnitch Network Statistics for</source> + <translation type="obsolete">Eventos de OpenSnitch de</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="761"/> + <source> Your are about to delete this rule. </source> + <translation> Sie sind im Begriff, diese Regel zu löschen. </translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1244"/> + <source> Are you sure?</source> + <translation> Bist du sicher?</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="568"/> + <source>OpenSnitch Network Statistics {0}</source> + <translation>OpenSnitch-Netzwerkstatistiken {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="570"/> + <source>OpenSnitch Network Statistics for {0}</source> + <translation>OpenSnitch-Netzwerkstatistiken für {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <translation type="obsolete">Name</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="294"/> + <source>Address</source> + <translation type="obsolete">Adresse</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="176"/> + <source>Status</source> + <translation type="obsolete">Status</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="177"/> + <source>Hostname</source> + <translation type="obsolete">Hostname</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="183"/> + <source>Version</source> + <translation type="obsolete">Version</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="180"/> + <source>Rules</source> + <translation type="obsolete">Regeln</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Time</source> + <translation type="obsolete">Zeit</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Action</source> + <translation type="obsolete">Aktion</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Duration</source> + <translation type="obsolete">Dauer</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Node</source> + <translation type="obsolete">Knoten</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="253"/> + <source>Hits</source> + <translation type="unfinished">Treffer</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>Protocol</source> + <translation type="obsolete">Protokoll</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1837"/> + <source>Save as CSV</source> + <translation>Als CSV speichern</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>Enabled</source> + <translation type="obsolete">Aktiviert</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="738"/> + <source>Delete</source> + <translation>Löschen</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="948"/> + <source>always</source> + <translation type="obsolete">siempre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="575"/> + <source><b>Error:</b><br><br>{0}</source> + <translation type="obsolete"><b>Error:</b><br><br>{0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="921"/> + <source><b>Error:</b><br><br></source> + <comment>{0}</comment> + <translation><b>Fehler:</b><br><br></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="928"/> + <source>Warning:</source> + <translation>Warnung:</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="717"/> + <source>Allow</source> + <translation>Erlauben</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="718"/> + <source>Deny</source> + <translation>Verweigern</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="722"/> + <source>Always</source> + <translation>Immer</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="723"/> + <source>Until reboot</source> + <translation>Bis zum Neustart</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="731"/> + <source>Disable</source> + <translation>Deaktivieren</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="733"/> + <source>Enable</source> + <translation>Aktivieren</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="736"/> + <source>Duplicate</source> + <translation>Duplizieren</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="737"/> + <source>Edit</source> + <translation>Bearbeiten</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="891"/> + <source>Rule not found by that name and node</source> + <translation>Regel von diesem Namen und Knoten nicht gefunden</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1244"/> + <source> You are about to delete this rule. </source> + <translation> Sie sind dabei, diese Regel zu löschen. </translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="308"/> + <source>Rule</source> + <translation type="obsolete">Regel</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <comment>xxxxx</comment> + <translation type="obsolete">Name</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Name</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="294"/> + <source>Address</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Adresse</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="295"/> + <source>Status</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Status</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="296"/> + <source>Hostname</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Hostname</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="297"/> + <source>Version</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Version</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="298"/> + <source>Rules</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Regeln</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Time</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Zeit</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Action</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Aktion</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Duration</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Dauer</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Node</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Knoten</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>Enabled</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Aktiviert</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="304"/> + <source>Hits</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Treffer</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>Protocol</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Protokoll</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="308"/> + <source>Rule</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Regel</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="392"/> + <source>Name</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Name</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="288"/> + <source>Address</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Adresse</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="379"/> + <source>Status</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Status</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="380"/> + <source>Hostname</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Hostname</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="386"/> + <source>Version</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Version</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="383"/> + <source>Rules</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Regeln</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="390"/> + <source>Time</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Zeit</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="395"/> + <source>Action</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Aktion</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="396"/> + <source>Duration</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Dauer</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="391"/> + <source>Node</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Knoten</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="393"/> + <source>Enabled</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Aktiviert</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="405"/> + <source>Hits</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Treffer</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Protocol</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Protokoll</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Process</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Prozess</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Destination</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Ziel</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="306"/> + <source>Rule</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Regel</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="307"/> + <source>UserID</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>BenutzerID</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="377"/> + <source>LastConnection</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>LetzteVerbindung</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Args</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>DstIP</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>ZielIP</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="304"/> + <source>DstHost</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>ZielHost</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>DstPort</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>ZielPort</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="174"/> + <source>LastConnection</source> + <translation type="obsolete">LetzteVerbindung</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="179"/> + <source>Uptime</source> + <translation type="obsolete">Betriebszeit</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="181"/> + <source>Connections</source> + <translation type="obsolete">Verbindungen</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="182"/> + <source>Dropped</source> + <translation type="obsolete">Abgelehnt</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="252"/> + <source>What</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="709"/> + <source>Apply to</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="719"/> + <source>Reject</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="25"/> + <source>Network name</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="378"/> + <source>Addr</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="382"/> + <source>Uptime</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">Betriebszeit</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="384"/> + <source>Connections</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">Verbindungen</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="385"/> + <source>Dropped</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">Abgelehnt</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="404"/> + <source>What</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="394"/> + <source>Precedence</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="644"/> + <source>New node connected</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>stats_deleterule</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="774"/> + <source> Your are about to delete this rule. </source> + <translation type="obsolete"> Estás a punto de borrar esta regla. </translation> + </message> +</context> +<context> + <name>stats_deleterule2</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="776"/> + <source> Are you sure?</source> + <translation type="obsolete"> ¿Estás seguro?</translation> + </message> +</context> +<context> + <name>stats_disabled</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="74"/> + <source>Disabled</source> + <translation type="obsolete">Deshabilitado</translation> + </message> +</context> +<context> + <name>stats_notrunning</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="73"/> + <source>Not running</source> + <translation type="obsolete">Parado</translation> + </message> +</context> +<context> + <name>stats_running</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="75"/> + <source>Running</source> + <translation type="obsolete">Interceptando</translation> + </message> +</context> +<context> + <name>stats_wintitle</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="409"/> + <source>OpenSnitch Network Statistics</source> + <translation type="obsolete">Eventos de red OpenSnitch</translation> + </message> +</context> +<context> + <name>stats_wintitle2</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="411"/> + <source>OpenSnitch Network Statistics for</source> + <translation type="obsolete">Eventos de OpenSnitch de</translation> + </message> +</context> +</TS> diff --git a/ui/i18n/locales/es_ES/opensnitch-es_ES.ts b/ui/i18n/locales/es_ES/opensnitch-es_ES.ts new file mode 100644 index 0000000..7385541 --- /dev/null +++ b/ui/i18n/locales/es_ES/opensnitch-es_ES.ts @@ -0,0 +1,2435 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="es_ES"> +<context> + <name>Dialog</name> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="34"/> + <source>opensnitch-qt</source> + <translation>opensnitch-qt</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="299"/> + <source>User ID</source> + <translation>UID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="333"/> + <source><html><head/><body><p><span style=" font-weight:600;">Executed from</span></p></body></html></source> + <translation>Ejecutado desde</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="630"/> + <source>TextLabel</source> + <translation>Etiqueta de texto</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="426"/> + <source>Source IP</source> + <translation>IP origen</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="449"/> + <source>Process ID</source> + <translation>PID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="582"/> + <source>Destination IP</source> + <translation>IP destino</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="605"/> + <source>Dst Port</source> + <translation>Puerto destino</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="679"/> + <source>from this executable</source> + <translation>de este ejecutable</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="684"/> + <source>from this command line</source> + <translation>de este comando</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="689"/> + <source>this destination port</source> + <translation>este puerto destino</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="694"/> + <source>this user</source> + <translation>este usuario</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="699"/> + <source>this destination ip</source> + <translation>esta IP destino</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="728"/> + <source>once</source> + <translation>una vez</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="733"/> + <source>30s</source> + <translation>30 segundos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="738"/> + <source>5m</source> + <translation>5 minutos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="743"/> + <source>15m</source> + <translation>15 minutos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="748"/> + <source>30m</source> + <translation>30 minutos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="753"/> + <source>1h</source> + <translation>1 hora</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="706"/> + <source>for this session</source> + <translation type="obsolete">durante esta sesión</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="763"/> + <source>forever</source> + <translation>para siempre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="789"/> + <source>Deny</source> + <translation>Denegar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="818"/> + <source>Allow</source> + <translation>Permitir</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="847"/> + <source>+</source> + <translation>+</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="758"/> + <source>until reboot</source> + <translation>Hasta reiniciar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="704"/> + <source>from this PID</source> + <translation>a partir de este PID</translation> + </message> +</context> +<context> + <name>PreferencesDialog</name> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="14"/> + <source>Preferences</source> + <translation>Preferencias</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="472"/> + <source>UI</source> + <translation>UI</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="54"/> + <source><html><head/><body><p>This timeout is the countdown you see when a pop-up dialog is shown.</p></body></html></source> + <translation type="obsolete">Este timeout es la cuenta atrás que aparece cuando se muestra una ventana emergente</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="454"/> + <source>Default timeout</source> + <translation>Timeout por defecto</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="331"/> + <source>Pop-up default duration</source> + <translation>Duración por defecto (de la acción/regla)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="754"/> + <source>Default duration</source> + <translation>Duración por defecto</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="162"/> + <source>Pop-up default action</source> + <translation type="obsolete">Acción por defecto de la ventana emergente</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="483"/> + <source>Default action</source> + <translation type="obsolete">Acción por defecto</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="314"/> + <source>Default target</source> + <translation>Filtrado por defecto</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="170"/> + <source>center</source> + <translation>centro</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="175"/> + <source>top right</source> + <translation>Arriba a la derecha</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="180"/> + <source>bottom right</source> + <translation>Abajo a la derecha</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="185"/> + <source>top left</source> + <translation>Arriba a la izquierda</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="190"/> + <source>bottom left</source> + <translation>Abajo a la izquierda</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="167"/> + <source>Prompt dialog default position on screen</source> + <translation type="obsolete">Posición por defecto</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="274"/> + <source>by executable</source> + <translation>por ejecutable</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="279"/> + <source>by command line</source> + <translation>por comando</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="284"/> + <source>by destination port</source> + <translation>por puerto destino</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="289"/> + <source>by destination ip</source> + <translation>por IP destino</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="294"/> + <source>by user id</source> + <translation>por UID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="863"/> + <source>once</source> + <translation>una vez</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="210"/> + <source>30s</source> + <translation>30 segundos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="215"/> + <source>5m</source> + <translation>5 minutos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="220"/> + <source>15m</source> + <translation>15 minutos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="225"/> + <source>30m</source> + <translation>30 minutos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="230"/> + <source>1h</source> + <translation>1 hora</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="240"/> + <source>for this session</source> + <translation type="obsolete">durante esta sesión</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="240"/> + <source>forever</source> + <translation>para siempre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="905"/> + <source>deny</source> + <translation>Denegar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="914"/> + <source>allow</source> + <translation>Permitir</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="406"/> + <source>Disable pop-ups, only display an alert</source> + <translation type="obsolete">Deshabilitar ventanas emergentes, +sólo mostrar alerta</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="711"/> + <source>Nodes</source> + <translation>Nodos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="717"/> + <source>Process monitor method</source> + <translation>Método parar monitorizar procesos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="751"/> + <source><html><head/><body><p>The default duration will take place when there's no UI connected.</p></body></html></source> + <translation>La Duración por defecto se aplicará cuando no haya ninguna UI conectada</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="881"/> + <source><html><head/><body><p>Address of the node.</p><p>Default: unix:///tmp/osui.sock (unix:// is mandatory if it's a Unix socket)</p><p>It can also be an IP address with the port: 127.0.0.1:50051</p></body></html></source> + <translation><html><head/><body><p>Dirección del nodo.</p><p>Por defecto: unix:///tmp/osui.sock (unix:// es obligatorio si es un socket Unix)</p><p>También puede ser una IP con este formato: 127.0.0.1:50051, 192.168.1.122:12345, etc..</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="884"/> + <source>Address</source> + <translation>Dirección</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1024"/> + <source>Default log level</source> + <translation>Nivel de log por defecto</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="932"/> + <source>Version</source> + <translation>Versión</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="790"/> + <source><html><head/><body><p>The default action will take place when there's no UI connected.</p></body></html></source> + <translation>La Acción por defecto se aplicará cuando no haya ninguna UI conectada</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="734"/> + <source><html><head/><body><p>Log file to write logs.<br/></p><p>/dev/stdout will print logs to the standard output.</p></body></html></source> + <translation><html><head/><body><p>Fichero en el que escribir los logs.<br/></p><p>/dev/stdout escribirá los logs por la salida estándar del servicio.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="737"/> + <source>Log file</source> + <translation>Fichero de log</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="578"/> + <source><html><head/><body><p>If checked, opensnitch will prompt you to allow or deny connections that don't have an asocciated PID, due to several reasons.</p><p>The pop-up dialog will only contain information about the network connection.</p></body></html></source> + <translation type="obsolete">Si marcas esta opción, OpenSnitch te preguntará para Aceptar o Denegar conexiones que no tengan un PID asociado por diferentes razones. + +La ventana emergente sólo contendrá información relativa a la conexión. + +Nota: Estas conexiones no tienen por qué indicar que algo sospechoso está sucediendo. Simplemente +es que no hemos descubierto el PID (por ejemplo conexiones que no se originan en la máquina, o paquetes en mal estado).</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="581"/> + <source>Intercept Unknown Connections</source> + <translation type="obsolete">Interceptar conexiones desconocidas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="809"/> + <source>HostName</source> + <translation>Nombre del host</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="983"/> + <source>unix:///tmp/osui.sock</source> + <translation>unix:///tmp/osui.sock</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="868"/> + <source>until restart</source> + <translation>hasta reiniciar (el servicio)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="873"/> + <source>always</source> + <translation>siempre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="995"/> + <source>/var/log/opensnitchd.log</source> + <translation>/var/log/opensnitchd.log</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1000"/> + <source>/dev/stdout</source> + <translation>/dev/stdout</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="767"/> + <source>Apply configuration to all nodes</source> + <translation>Aplicar configuración a todos +los nodos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1039"/> + <source>Database</source> + <translation>Datos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1074"/> + <source>In memory</source> + <translation>En memoria</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1079"/> + <source>File</source> + <translation>Fichero</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1345"/> + <source>Close</source> + <translation>Cerrar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1356"/> + <source>Apply</source> + <translation>Aplicar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1367"/> + <source>Save</source> + <translation>Guardar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="235"/> + <source>until reboot</source> + <translation>Hasta reiniciar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1093"/> + <source>Database type</source> + <translation>Tipo de base de datos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1100"/> + <source>Select</source> + <translation>Seleccionar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="83"/> + <source>Pop-ups default options</source> + <translation type="obsolete">Opciones por defecto de las ventanas emergentes</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="367"/> + <source>Pop-ups default position on screen</source> + <translation type="obsolete">Posición en pantalla</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="102"/> + <source><html><head/><body><p>The advanced view allows you to apply more filters on a connection</p><p>when a pop-up appears.</p></body></html></source> + <translation type="obsolete">La vista avanzada permite filtrar conexiones por más parámetros</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="350"/> + <source>Show advanced view by default</source> + <translation>Mostrar vista avanzada por defecto</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="665"/> + <source>Action</source> + <translation>Acción</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="366"/> + <source><html><head/><body><p>If checked, the pop-ups will be displayed with the advanced view active.</p></body></html></source> + <translation>Si se selecciona, las ventanas emergentes se mostrarán con la vista avanzada activada</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="334"/> + <source>Duration</source> + <translation>Duración</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="254"/> + <source><html><head/><body><p>By default when a new pop-up appears, in its simplest form, you'll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).</p><p>With these options, you can choose multiple fields to filter connections for.</p></body></html></source> + <translation>Por defecto cuando una ventana emergente aparece, en su forma más simple, puedes filtrar conexiones por un +parámetro de la conexión (ejecutable, puerto, IP, etc). + +Con estas opciones, puedes seleccionar varios campos por los que filtrar por defecto conexiones</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="257"/> + <source>Filter connections also by:</source> + <translation>Filtrar conexiones también por:</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="362"/> + <source>If checked, this field will be checked when a pop-up is displayed</source> + <translation type="obsolete">Si lo seleccionas, este campo se usará para filtrar las conexiones</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="81"/> + <source>User ID</source> + <translation>UID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="97"/> + <source>Destination port</source> + <translation>Puerto destino</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="113"/> + <source>Destination IP</source> + <translation>IP destino</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="451"/> + <source><html><head/><body><p>This timeout is the countdown you see when a pop-up dialog is shown.</p><p>If the pop-up is not answered, the default options will be applied.</p></body></html></source> + <translation>Este timeout es la cuenta atrás que ves cuando se muestra una ventana emergente + +Si no respondes a la ventana emergente, se aplicarán las opciones por defecto</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="347"/> + <source>The advanced view allows you to easily select multiple fields to filter connections</source> + <translation>La vista avanzada te permite seleccionar fácilmente múltiples campos para filtrar conexiones</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="110"/> + <source>If checked, this field will be selected when a pop-up is displayed</source> + <translation>Si se selecciona, este campo estará marcado cuando una ventana emergente aparezca</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="150"/> + <source><html><head/><body><p>Pop-up default action.</p><p>When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.</p><p><br/></p><p>While a pop-up is asking the user to allow or deny a connection:</p><p>1. new outgoing connections are denied.</p><p>2. known connections are allowed or denied based on the rules defined by the user.</p></body></html></source> + <translation><html><head/><body><p>Acción por defecto de la ventana emergente.</p><p>Cuando una nueva conexión saliente está a punto de establecerse, esta acción será la predeterminada, por lo que si llega el timeout, está será la que se aplique.</p><p><br/></p><p>Mientras una ventana emergente está activa esperando ser aprobada o denegada:</p><p>1. Las nuevas conexiones salientes son denegadas (según la configuración del demonio)</p><p>2. Las conexiones ya conocidas se permitirán o denegarán en base a las reglas ya creadas por el usuario.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="793"/> + <source>Default action when the GUI is disconnected</source> + <translation>Opción por defecto cuando la GUI no está conectada</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="894"/> + <source>Debug invalid connections</source> + <translation>Depurar conexiones inválidas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="39"/> + <source>Pop-ups</source> + <translation>Ventanas emergentes</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="64"/> + <source>Default options</source> + <translation>Opciones por defecto</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="321"/> + <source>Default position on screen</source> + <translation>Posición por defecto en la pantalla</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="479"/> + <source>any temporary rules</source> + <translation>cualquier regla temporal</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="492"/> + <source><html><head/><body><p>When this option is selected, the rules of the selected duration won't be added to the list of temporary rules in the GUI.</p><p><br/></p><p>Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</p></body></html></source> + <translation><html><head/><body><p>Cuando esta opción está seleccionada, las reglas de la duración elegida no se añadirán a la lista de reglas temporales en la GUI.</p><p><br/></p><p>Las reglas temporales seguirán siendo válidas, y puedes usarlas cuando se pregunte para permitir o denegar una nueva conexión.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="495"/> + <source>Don't save rules of duration</source> + <translation>No guardar reglas de duración</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="463"/> + <source>Show events columns</source> + <translation type="obsolete">Mostrar columnas de la pestaña Eventos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="601"/> + <source>Time</source> + <translation>Hora</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="681"/> + <source>Destination</source> + <translation>Destino</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="649"/> + <source>Protocol</source> + <translation>Protocolo</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="697"/> + <source>Process</source> + <translation>Aplicación</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="617"/> + <source>Rule</source> + <translation>Regla</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="633"/> + <source>Node</source> + <translation>Nodo</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="723"/> + <source><html><head/><body><p>If checked, opensnitch will prompt you to allow or deny connections that don't have an asocciated PID, due to several reasons, mostly due to bad state connections.</p><p>The pop-up dialog will only contain information about the network connection.</p><p>There're some scenarios where these are valid connections though, like when establishing a VPN using wireguard.</p></body></html></source> + <translation type="obsolete"><html><head/><body><p>Si se selecciona opensnitch te preguntará para permitir o denegar conexiones que no tienen un PID asociado. Esto puede pasar por diferentes motivos, principalmente debido a conexiones inválidas.</p><p>La ventana emergente sólo contendrá información de la conexión.</p><p>Hay algunas situaciones en las que estas conexiones son válidas, por ejemplo al establecer un túnel VPN con wireguard.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="589"/> + <source>Events tab columns</source> + <translation>Columnas de la pestaña Eventos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="299"/> + <source>by PID</source> + <translation>por PID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="461"/> + <source>Disable pop-ups, only display an notification</source> + <translation>Deshabilitar ventanas emergentes, sólo mostrar notificaciones</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="508"/> + <source>Desktop notifications</source> + <translation>Notificaciones de escritorio</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="526"/> + <source>Use system notifications</source> + <translation>Usar notificaciones del sistema</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="542"/> + <source>Use Qt notifications</source> + <translation>Usar notificaciones de Qt</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="571"/> + <source>Test</source> + <translation>Probar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="891"/> + <source><html><head/><body><p>If checked, OpenSnitch will prompt you to allow or deny connections that don't have an associated PID, due to several reasons, mostly due to bad state connections.</p><p>The pop-up dialog will only contain information about the network connection.</p><p>There're some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.</p></body></html></source> + <translation><html><head/><body><p>Si lo marcas, OpenSnitch sólo te preguntará para denegar o permitir conexiones que por diversas razones no tengan un PID/aplicación asociado. Generalmente son conexiones en estado erróneo.</p><p>La ventana emergente sólo contendrá información sobre la conexión de red.</p><p>Hay algunos casos en los que estas conexiones pueden ser válidas, como cuando se establecen conexiones VPN.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1178"/> + <source>minutes</source> + <translation>minutos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1204"/> + <source>Minutes between events purges</source> + <translation>Minutos entre borrado de eventos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1227"/> + <source>days</source> + <translation>días</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1237"/> + <source>Maximum days of events to keep</source> + <translation>Máximo de días a guardar</translation> + </message> +</context> +<context> + <name>ProcessDetailsDialog</name> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="14"/> + <source>Process details</source> + <translation>Detalles del proceso</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="61"/> + <source>loading...</source> + <translation>cargando...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="81"/> + <source>CWD: loading...</source> + <translation>CWD: cargando...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="93"/> + <source>mem stats: loading...</source> + <translation>estadísticas de memoria: cargando...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="121"/> + <source>Status</source> + <translation>Estado</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="135"/> + <source>Open files</source> + <translation>Ficheros abiertos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="149"/> + <source>I/O Statistics</source> + <translation>Estadísticas Entrada/Salida</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="163"/> + <source>Memory mapped files</source> + <translation>Ficheros cargados en memoria</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="177"/> + <source>Stack</source> + <translation>Pila</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="191"/> + <source>Environment variables</source> + <translation>Variables de entorno</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="210"/> + <source>Application pids</source> + <translation>PIDs de la aplicación</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="240"/> + <source>Start or stop monitoring this process</source> + <translation>Iniciar o Parar el monitorizado de este proceso</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="256"/> + <source>Close</source> + <translation>Cerrar</translation> + </message> +</context> +<context> + <name>RulesDialog</name> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="14"/> + <source>Rule</source> + <translation>Regla</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="118"/> + <source>Node</source> + <translation>Nodo</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="141"/> + <source>Apply rule to all nodes</source> + <translation>Aplicar regla a todos los nodos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="239"/> + <source>From this command line</source> + <translation>De este comando</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="206"/> + <source>From this executable</source> + <translation>De este ejecutable</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="751"/> + <source>Action</source> + <translation>Acción</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="138"/> + <source>/path/to/executable, .*/bin/executable[0-9\.]+$, ...</source> + <translation type="obsolete">/ruta/al/ejecutable, .*/bin/executable[0-9\.]+$, ...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="383"/> + <source>To this IP / Network</source> + <translation>A esta IP/Red</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="792"/> + <source>once</source> + <translation>una vez</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="797"/> + <source>30s</source> + <translation>30s</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="802"/> + <source>5m</source> + <translation>5m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="807"/> + <source>15m</source> + <translation>15m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="812"/> + <source>30m</source> + <translation>30m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="817"/> + <source>1h</source> + <translation>1h</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="230"/> + <source>until restart</source> + <translation type="obsolete">hasta reiniciar (el servicio)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="827"/> + <source>always</source> + <translation>siempre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="293"/> + <source>To this port</source> + <translation>A este puerto</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="169"/> + <source>From this user ID</source> + <translation>De este UID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="419"/> + <source>Commas or spaces are not allowed to specify multiple domains. + +Use regular expressions instead: +.*(opensnitch|duckduckgo).com +.*\.google.com + +or a single domain: +www.gnu.org - it'll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ... +gnu.org - it'll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source> + <translation>No se permiten ni comas ni espacios para especificar múltiples dominios. + +Puedes usar expresiones regulares en su lugar: + +.*(opensnitch|duckduckgo).com +.*\.google.com + +o un único dominio: +www.gnu.org - sólo filtrará www.gnu.org, NO filtrará ftp.gnu.org ni www2.gnu.org +gnu.org - sólo filtrará gnu.org, ni www.gnu.org, ni ftp. gnu.org, ...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="430"/> + <source>www.domain.org, .*\.domain.org</source> + <translation>www.dominio.org, .*\.dominio.org</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="323"/> + <source><html><head/><body><p>Only TCP, UDP or UDPLITE are allowed</p><p>You can use regexp, i.e.: ^(TCP|UDP)$</p></body></html></source> + <translation>Sólo se permiten las opciones TCP, UDP o UDPLITE. Puedes usar expresiones regulares +sobre estas opciones, por ejemplo TCP o UDP: ^(TCP|UDP)$</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="333"/> + <source>TCP</source> + <translation>TCP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="338"/> + <source>UDP</source> + <translation>UDP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="343"/> + <source>UDPLITE</source> + <translation>UDPLITE</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="348"/> + <source>TCP6</source> + <translation>TCP6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="353"/> + <source>UDP6</source> + <translation>UDP6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="358"/> + <source>UDPLITE6</source> + <translation>UDPLITE6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="440"/> + <source>You can specify a single IP: +- 192.168.1.1 + +or a regular expression: +- 192\.168\.1\.[0-9]+ + +multiple IPs: +- ^(192\.168\.1\.1|172\.16\.0\.1)$ + +You can also specify a subnet: +- 192.168.1.0/24 + +Note: Commas or spaces are not allowed to separate IPs or networks.</source> + <translation>Puedes especificar una IP: +- 192.168.1.1 + +o una expresión regular: +- 192\.168\.1\.[0-9]+ + +múltiples IPs: +- ^(192\.168\.1\.1|172\.16\.0\.1)$ + +También puedes especificar una subnet: +- 192.168.1.0/24 + +Nota: No se permiten ni comas ni espacios para especificar IPs o redes.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="459"/> + <source>LAN</source> + <translation>LAN</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="464"/> + <source>127.0.0.0/8</source> + <translation>127.0.0.0/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="469"/> + <source>192.168.0.0/24</source> + <translation>192.168.0.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="474"/> + <source>192.168.1.0/24</source> + <translation>192.168.1.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="479"/> + <source>192.168.2.0/24</source> + <translation>192.168.2.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="484"/> + <source>192.168.0.0/16</source> + <translation>192.168.0.0/16</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="489"/> + <source>169.254.0.0/16</source> + <translation>169.254.0.0/16</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="494"/> + <source>172.16.0.0/12</source> + <translation>172.16.0.0/12</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="499"/> + <source>10.0.0.0/8</source> + <translation>10.0.0.0/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="504"/> + <source>::1/128</source> + <translation>::1/128</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="509"/> + <source>fc00::/7</source> + <translation>fc00::/7</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="514"/> + <source>ff00::/8</source> + <translation>ff00::/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="519"/> + <source>fe80::/10</source> + <translation>fe80::/10</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="524"/> + <source>fd00::/8</source> + <translation>fd00::/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="784"/> + <source>Duration</source> + <translation>Duración</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="376"/> + <source>Protocol</source> + <translation>Protocolo</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="300"/> + <source>To this host</source> + <translation>A este host</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="843"/> + <source>Deny</source> + <translation>Denegar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="877"/> + <source>Allow</source> + <translation>Permitir</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="899"/> + <source>Name</source> + <translation>Nombre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="22"/> + <source>Enable</source> + <translation>Habilitar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="706"/> + <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them. + +000-allow-localhost +001-deny-broadcast +...</source> + <translation>Las reglas se comprueban en orden alfabético, por lo que debes nombrarlas así para priorizarlas. + +000-allow-localhost +0001-deny-broadcast +...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="713"/> + <source>leave blank to autocreate</source> + <translation>dejar en blanco para autoasignar nombre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="46"/> + <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one. + +You must name the rule in such manner that it'll be checked first, because they're checked in alphabetical order. For example: + +[x] Priority - 000-priority-rule +[ ] Priority - 001-less-priority-rule</source> + <translation>Si marcas esta opción, esta regla tendrá prioridad sobre el resto de reglas cuando le toque evaluarla. No se comprobará ninguna regla más después de esta si coincide. + +Debes nombrar la regla de tal manera que se compruebe de las primeras, ya que se nombran alfabéticamente. Por ejemplo: + +[x] Prioritaria-000-regla-prioritaria +[ ] Prioritaria-001-regla-menos-prioritaria</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="54"/> + <source>Priority rule</source> + <translation>Prioritaria</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="74"/> + <source><html><head/><body><p>By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.<br/></p><p>If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.</p></body></html></source> + <translation><html><head/><body><p>Por defecto los campos de una regla NO son sensibles a mayúsculas/minúsculas, es decir, si un proceso trata de acceder a gOOgle.CoM y tienes una regla para Denegar .*google.com, la conexión será bloqueada. <br/></p><p>Si marcas esta opción y quieres bloquear EXACTAMENTE gOOgle.CoM, tendrás que especificar en el campo de la regla el dominio exacto que quieres filtrar (en este caso: gOOgle.CoM).</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="77"/> + <source>Case-sensitive</source> + <translation>Distinguir mayúsculas/minúsculas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="369"/> + <source><html><head/><body><p>You can specify multiple ports using regular expressions:</p><p><br/></p><p>- 53, 80 or 443:</p><p>^(53|80|443)$</p><p><br/></p><p>- 53, 443 or 5551, 5552, 5553, etc:</p><p>^(53|443|555[0-9])$</p></body></html></source> + <translation>Puedes especificar múltiples puertos usando expresiones regulares: + +- 53, 80 o 443: +^(53|80|443)$ + +- 53, 443 o 5551, 5552, 5553, etc: +^(53|443|555[0-9])$</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="822"/> + <source>until reboot</source> + <translation>Hasta reiniciar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="583"/> + <source>To this list of domains</source> + <translation>A esta lista de dominios</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="539"/> + <source><html><head/><body><p>Select a directory with lists of domains to block or allow.</p><p>Put inside that directory files with any extension containing lists of domains.</p><p><br/>The format of each entry of a list is as follow (hosts format):</p><p>127.0.0.1 www.domain.com</p><p>or </p><p>0.0.0.0 www.domain.com</p></body></html></source> + <translation type="obsolete"><html><head/><body><p>Selecciona un directorio con listas de dominios para permitir o denegar.</p><p>Mete dentro de este directorio ficheros con cualquier extensión que contengan listas de dominios.</p><p><br/>El formato de cada dominio de la lista tiene que estar en formato hosts, así:</p><p>127.0.0.1 www.domain.com</p><p>o </p><p>0.0.0.0 www.domain.com</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="163"/> + <source>Applications</source> + <translation>Aplicaciones</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="216"/> + <source><html><head/><body><p>This field will only match the executable path. It is not modifiable by the user.<br/></p><p>You can use regular expressions to deny executions from /tmp for example:<br/></p><p>^/tmp/.*$</p></body></html></source> + <translation><html><head/><body><p>Este campo sólo comprueba la ruta del ejecutable (la cual no es modificable por el usuario).<br/></p><p>Puedes usar expresiones regulares para denegar cualquier ejecución desde /tmp, por ejemplo; ^/tmp/.*$</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="229"/> + <source><html><head/><body><p>This field will contain and match the command line that was executed by the user.<br/></p><p>If the user typed the command, only the command will appear:</p><p>telnet 1.2.3.4<br/></p><p>If the user typed the absolute or relative path to the command, that is what will appear:</p><p>/usr/bin/telnet 1.2.3.4</p><p>../../../usr/bin/telnet 1.2.3.4</p></body></html></source> + <translation><html><head/><body><p>Este campo contendrá y comprobará solamente la linea de comandos tecleada por el usuario.<br/></p><p>Si el usuario sólo escribió el comando (sin la ruta absoluta), sólo aparecerá el comando:</p><p>telnet 1.2.3.4<br/></p><p>Si el usuario escribió la ruta absoluta o relativa al comando, eso es lo que aparecerá:</p><p>/usr/bin/telnet 1.2.3.4</p><p>../../../usr/bin/telnet 1.2.3.4</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="246"/> + <source>From this PID</source> + <translation>De este PID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="287"/> + <source>Network</source> + <translation>Red</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="536"/> + <source>List of domains/IPs</source> + <translation>A esta lista de dominios/IPs</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="542"/> + <source>To this list of network ranges</source> + <translation>A esta lista de rangos de red</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="549"/> + <source>To this list of IPs</source> + <translation>A esta lista de IPs</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="574"/> + <source><html><head/><body><p>Select a directory with files containing list of IPs to block or allow:</p><p>1.2.3.4.5</p><p>1.2.3.4.6</p><p>.</p><p>etc.</p><p>One IP per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation><html><head/><body><p>Selecciona un directorio con ficheros que contengan listas de IPs a bloquear o permitir:</p><p>1.2.3.4.5</p><p>1.2.3.4.6</p><p>.</p><p>etc.</p><p>Una IP por linea. Las lineas en blanco o que comiencen con # serán ignoradas.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="608"/> + <source><html><head/><body><p>Select a directory with files containing list of network ranges to block or allow:</p><p>1.2.3.0/24</p><p>80.34.56.0/20</p><p>.</p><p>etc.<br/></p><p>One Network Range per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation><html><head/><body><p>Selecciona un directorio con ficheros que contengan listas de rangos de red a bloquear o permitir:</p><p>1.2.3.0/24</p><p>80.34.56.0/20</p><p>.</p><p>etc.<br/></p><p>Un rango de red por linea. Las lineas en blanco o que comiencen con # serán ignoradas.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="636"/> + <source><html><head/><body><p>Select a directory with lists of domains to block or allow.</p><p>Put inside that directory files with any extension containing lists of domains.</p><p><br/>The format of each entry of a list is as follow (hosts format):</p><p>127.0.0.1 www.domain.com</p><p>or </p><p>0.0.0.0 www.domain.com</p><p>Empty lines or started with # are ignored.</p></body></html></source> + <translation><html><head/><body><p>Selecciona un directorio con ficheros que contengan listas de dominios a bloquear o permitir.</p><p>Los ficheros de ese directorio pueden tener cualquier extensión.</p><p><br/>El formato de cada linea es como sigue (formato hosts):</p><p>127.0.0.1 www.domain.com</p><p>or </p><p>0.0.0.0 www.domain.com</p><p>Las lineas en blanco o que comiencen con # serán ignoradas.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="651"/> + <source>To this list of domains +(regular expressions)</source> + <translation>A esta lista de dominios +(expresiones regulares)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="677"/> + <source><html><head/><body><p>Select a directory with files containing regular expressions of domains to block or allow:</p><p>.*\.example\.com</p><p>You can also use a domain as is: &quot;example.com&quot; , and it'll match whatever.example.com, whatever.example.com.localdomain, etc.</p><p>One domain per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation><html><head/><body><p>Selecciona un directorio con ficheros que contengan expresiones regulares de dominios para bloquear o permitir:</p><p>.*\.example\.com</p><p>También puedes usar un dominio literal (sin expresión regular): &quot;example.com&quot; ,comprobará whatever.example.com, whatever.example.com.localdomain, etc.</p><p>Un dominio por linea. Las lineas en blanco o que comiencen con # serán ignoradas</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="857"/> + <source>Reject</source> + <translation>Rechazar</translation> + </message> +</context> +<context> + <name>StatsDialog</name> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="34"/> + <source>OpenSnitch Network Statistics</source> + <translation>Eventos de red - OpenSnitch</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="290"/> + <source>Save to CSV.</source> + <translation>Exportar a CSV.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="300"/> + <source>Ctrl+S</source> + <translation>Ctrl+S</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="351"/> + <source>Create a new rule</source> + <translation>Crear una nueva regla</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="381"/> + <source><html><head/><body><p><span style=" font-size:11pt; font-weight:600;">hostname - 192.168.1.1</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-size:11pt; font-weight:600;">Nombre del host - 192.168.1.1</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="420"/> + <source>Status</source> + <translation>Estado</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1665"/> + <source>-</source> + <translation>-</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="464"/> + <source>Start or Stop interception</source> + <translation>Parar o iniciar la interceptación</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="509"/> + <source>Events</source> + <translation>Eventos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="94"/> + <source>Filter</source> + <translation>Filtrar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="107"/> + <source>Allow</source> + <translation>Permitir</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="116"/> + <source>Deny</source> + <translation>Denegar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="143"/> + <source>Ex.: firefox</source> + <translation>Ejemplo: firefox</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="199"/> + <source>50</source> + <translation>50</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="204"/> + <source>100</source> + <translation>100</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="209"/> + <source>200</source> + <translation>200</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="214"/> + <source>300</source> + <translation>300</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="748"/> + <source>Nodes</source> + <translation>Nodos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="554"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on the Addr column to view details of a node)</span></p></body></html></source> + <translation type="obsolete">(doble click en la columna Dirección para ver los detalles)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1569"/> + <source>Rules</source> + <translation>Reglas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="857"/> + <source>enable</source> + <translation>habilitar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="684"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on the Name column to view details of a rule)</span></p></body></html></source> + <translation type="obsolete">(doble click en la columna Nombre para ver los detalles)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="692"/> + <source>search rule name</source> + <translation type="obsolete">buscar regla</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="704"/> + <source>Application rules</source> + <translation>Reglas de aplicación</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="806"/> + <source>Permanent</source> + <translation>Permanentes</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="815"/> + <source>Temporary</source> + <translation>Temporales</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="933"/> + <source>Hosts</source> + <translation>Dominios</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1364"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click to view details of an item)</span></p></body></html></source> + <translation type="obsolete">(doble click en un dominio para ver detalles)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1020"/> + <source>Applications</source> + <translation>Aplicaciones</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1127"/> + <source>Addresses</source> + <translation>Direcciones</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1214"/> + <source>Ports</source> + <translation>Puertos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1298"/> + <source>Users</source> + <translation>Usuarios</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1404"/> + <source>Connections</source> + <translation>Conexiones</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1459"/> + <source>Dropped</source> + <translation>Rechazadas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1514"/> + <source>Uptime</source> + <translation>Tiempo de ejecución</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1639"/> + <source>Version</source> + <translation>Versión</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="227"/> + <source>Delete all intercepted events</source> + <translation>Borrar todos los eventos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="864"/> + <source>Edit rule</source> + <translation>Editar regla</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="878"/> + <source>Delete rule</source> + <translation>Borrar regla</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="926"/> + <source>Delete all intercepted hosts</source> + <translation type="obsolete">Borrar todos los hosts</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1051"/> + <source>Delete all intercepted applications</source> + <translation type="obsolete">Borrar todos las aplicaciones</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1159"/> + <source>Delete all intercepted addresses</source> + <translation type="obsolete">Borrar todas las direcciones</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1261"/> + <source>Delete all intercepted ports</source> + <translation type="obsolete">Borrar todos los puertos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1371"/> + <source>Delete all intercepted users</source> + <translation type="obsolete">Borrar todos los usuarios</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="699"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on a row to view details of a rule)</span></p></body></html></source> + <translation type="obsolete">(Doble click en una fila para editar una regla)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="912"/> + <source>Delete connections that matched this rule</source> + <translation>Borrar conexiones que coinciden con esta regla</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="797"/> + <source>All applications</source> + <translation>Todas las reglas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="125"/> + <source>Reject</source> + <translation>Rechazar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="177"/> + <source>0</source> + <translation>0</translation> + </message> +</context> +<context> + <name>contextual_menu</name> + <message> + <location filename="../../../opensnitch/service.py" line="43"/> + <source>Statistics</source> + <translation>Eventos</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="46"/> + <source>Help</source> + <translation>Ayuda</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="47"/> + <source>Close</source> + <translation>Cerrar</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="44"/> + <source>Enable</source> + <translation>Habilitar</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="45"/> + <source>Disable</source> + <translation>Deshabilitar</translation> + </message> +</context> +<context> + <name>menu_close</name> + <message> + <location filename="../../../opensnitch/service.py" line="131"/> + <source>Close</source> + <translation type="obsolete">Cerrar</translation> + </message> +</context> +<context> + <name>menu_help</name> + <message> + <location filename="../../../opensnitch/service.py" line="126"/> + <source>Help</source> + <translation type="obsolete">Ayuda</translation> + </message> +</context> +<context> + <name>menu_statistics</name> + <message> + <location filename="../../../opensnitch/service.py" line="120"/> + <source>Statistics</source> + <translation type="obsolete">Eventos</translation> + </message> +</context> +<context> + <name>notifications</name> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="518"/> + <source>System notifications are not available, you need to install python3-notify2.</source> + <translation>Las notificaciones de sistema no están disponibles, tienes que instalar python3-notify2.</translation> + </message> +</context> +<context> + <name>popups</name> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="91"/> + <source>Allow</source> + <translation>Permitir</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="92"/> + <source>Deny</source> + <translation>Denegar</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="54"/> + <source>forever</source> + <translation>para siempre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="285"/> + <source>Outgoing connection</source> + <translation>Conexión saliente</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="290"/> + <source>Process launched from:</source> + <translation>Proceso ejecutado desde:</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="325"/> + <source>from this command line</source> + <translation>este comando</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="321"/> + <source>from this executable</source> + <translation>este ejecutable</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="208"/> + <source>Unknown process</source> + <translation type="obsolete">Proceso no encontrado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="52"/> + <source>until reboot</source> + <translation>Hasta reiniciar</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="327"/> + <source>to port {0}</source> + <translation>puerto {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="222"/> + <source><b>%s</b> is connecting to <b>%s</b> on %s port %d</source> + <translation type="obsolete"><b>%s</b> está conectándose a <b>%s</b> en el puerto %s %d</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="228"/> + <source><b>Remote</b> process <b>%s</b> running on <b>%s</b> is connecting to <b>%s</b> on %s port %d</source> + <translation type="obsolete">El proceso <b>remoto %s</b> ejecutándose en <b>%s</b> está conectándose a <b>%s</b> en el puerto %s %d</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="390"/> + <source>to {0}</source> + <translation>a {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="330"/> + <source>from user {0}</source> + <translation>UID {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="347"/> + <source>to {0}.*</source> + <translation>a {0}.*</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="400"/> + <source>to *.{0}</source> + <translation>a *.{0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="378"/> + <source>to *{0}</source> + <translation type="obsolete">a *{0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="434"/> + <source><b>Remote</b> process %s running on <b>%s</b></source> + <translation>El proceso <b>Remoto</b> %s ejecutado en <b>%s</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="438"/> + <source>is connecting to <b>%s</b> on %s port %d</source> + <translation>está conectándose a <b>%s</b> en el puerto %s %d</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="444"/> + <source>is attempting to resolve <b>%s</b> via %s, %s port %d</source> + <translation>está tratando de resolver <b>%s</b> via %s, %s puerto %d</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="334"/> + <source>from this PID</source> + <translation>de este PID</translation> + </message> + <message> + <location filename="../../../opensnitch/notifications.py" line="105"/> + <source>New outgoing connection</source> + <translation>Nueva conexión saliente</translation> + </message> +</context> +<context> + <name>popups2</name> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="254"/> + <source><b>Remote</b> process <b>%s</b> running on <b>%s</b> is connecting to <b>%s</b> on %s port %d</source> + <translation type="obsolete">El proceso <b>remoto %s</b> ejecutándose en <b>%s</b> está conectándose a <b>%s</b> en el puerto %s %d</translation> + </message> +</context> +<context> + <name>preferences</name> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="171"/> + <source>Exception saving config: %s</source> + <translation type="obsolete">Error al guarda la configuración: %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="177"/> + <source>Applying configuration on %s ...</source> + <translation type="obsolete">Aplicando configuración en %s ...</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="208"/> + <source>Server address can not be empty</source> + <translation>La dirección del servidor no puede estar vacía</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="227"/> + <source>Error loading %s configuration</source> + <translation type="obsolete">Error al cargar la configuración %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="448"/> + <source>Configuration applied.</source> + <translation>Configuración aplicada.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="257"/> + <source>Error applying configuration: %s</source> + <translation type="obsolete">Error al aplicar la configuración: %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="299"/> + <source>Exception saving config: {0}</source> + <translation>Error al guardar la configuración: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="389"/> + <source>Applying configuration on {0} ...</source> + <translation>Aplicando configuración en {0} ...</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="238"/> + <source>Error loading {0} configuration</source> + <translation>Error al cargar la configuración {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="450"/> + <source>Error applying configuration: {0}</source> + <translation>Error al aplicar la configuración: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="323"/> + <source>Warning</source> + <translation>Aviso</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="323"/> + <source>You must select a file for the database<br>or choose "In memory" type.</source> + <translation>Debes seleccionar un fichero para la base de datos<br>o elegir el tipo En memoria.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="329"/> + <source>DB type changed</source> + <translation>El tipo de BBDD ha cambiado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="329"/> + <source>Restart the GUI in order effects to take effect</source> + <translation>Reinicia la GUI para que los cambios surtan efecto</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="480"/> + <source>Hover the mouse over the texts to display the help<br><br>Don't forget to visit the wiki: <a href="{0}">{0}</a></source> + <translation>Pasa el ratón sobre los textos para mostrar la ayuda<br><br>Y no olvides visitar el wiki: <a href="{0}">{0}</a></translation> + </message> +</context> +<context> + <name>proc_details</name> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="97"/> + <source><b>Error loading process information:</b> <br><br> + +</source> + <translation><b>Error al carga la información del proceso:</b> <br><br> + +</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="116"/> + <source><b>Error stopping monitoring process:</b><br><br></source> + <translation><b>Error al parar de monitorizar el proceso:</b><br><br></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="156"/> + <source>loading...</source> + <translation>cargando...</translation> + </message> +</context> +<context> + <name>rules</name> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="162"/> + <source>There're no nodes connected.</source> + <translation>No hay nodos conectados.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="203"/> + <source>Rule applied.</source> + <translation>Regla aplicada.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="123"/> + <source>Error applying rule: %s</source> + <translation type="obsolete">Error al aplicar la regla: %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="527"/> + <source>protocol can not be empty, or uncheck it</source> + <translation>el protocolo no puede estar vacío, o desmarca la opción</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="541"/> + <source>Protocol regexp error</source> + <translation>Error en la expresión regular del Protocolo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="545"/> + <source>process path can not be empty</source> + <translation>La ruta del ejecutable no puede estar vacía</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="559"/> + <source>Process path regexp error</source> + <translation>Error en la expresión regular del ejecutable</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="563"/> + <source>command line can not be empty</source> + <translation>El comando no puede estar vacío, o desmarca la opción</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="577"/> + <source>Command line regexp error</source> + <translation>Error en la expresión regular del Comando</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="581"/> + <source>Dest port can not be empty</source> + <translation>El puerto destino no puede estar vacío, o desmarca la opción</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="595"/> + <source>Dst port regexp error</source> + <translation>Error en la expresión regular del Puerto Destino</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="599"/> + <source>Dest host can not be empty</source> + <translation>El Host destino no puede estar vacío, o desmarca la opción</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="613"/> + <source>Dst host regexp error</source> + <translation>Error en la expresión regular del Host de destino</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="617"/> + <source>Dest IP/Network can not be empty</source> + <translation>La IP/Red de destino no puede estar vacía</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="639"/> + <source>Dst IP regexp error</source> + <translation>Error en la expresión regular de IP/Red de destino</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="651"/> + <source>User ID can not be empty</source> + <translation>El ID de Usuario no puede estar vacío, o desmarca la opción</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="665"/> + <source>User ID regexp error</source> + <translation>Error en la expresión regular del ID de Usuario</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="205"/> + <source>Error applying rule: {0}</source> + <translation>Error al aplicar la regla: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="739"/> + <source>Lists field cannot be empty</source> + <translation>El campo Listas de dominios no puede estar vacío</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="741"/> + <source>Lists field must be a directory</source> + <translation>El campo Listas debe ser un directorio</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="777"/> + <source><b>Rule not supported</b></source> + <translation><b>Tipo de regla no soportada</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="429"/> + <source><b>Error loading rule</b></source> + <translation><b>Error cargando la regla</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="179"/> + <source>There's already a rule with this name.</source> + <translation>Ya hay una regla con este nombre.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="669"/> + <source>PID field can not be empty</source> + <translation>El campo de PID no puede estar vacío</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="683"/> + <source>PID field regexp error</source> + <translation>Error en la expresión regular del PID</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="764"/> + <source>Select at least one field.</source> + <translation>Selecciona al menos un campo.</translation> + </message> +</context> +<context> + <name>stats</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="310"/> + <source>Not running</source> + <translation>Parado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="311"/> + <source>Disabled</source> + <translation>Deshabilitado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="312"/> + <source>Running</source> + <translation>Interceptando</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="412"/> + <source>OpenSnitch Network Statistics</source> + <translation type="obsolete">Eventos de OpenSnitch</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="414"/> + <source>OpenSnitch Network Statistics for</source> + <translation type="obsolete">Eventos de OpenSnitch de</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="761"/> + <source> Your are about to delete this rule. </source> + <translation> Estás a punto de borrar esta regla. </translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1244"/> + <source> Are you sure?</source> + <translation> ¿Estás seguro?</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="568"/> + <source>OpenSnitch Network Statistics {0}</source> + <translation>Eventos de red OpenSnitch {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="570"/> + <source>OpenSnitch Network Statistics for {0}</source> + <translation>Eventos de red OpenSnitch de {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <translation type="obsolete">Nombre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="294"/> + <source>Address</source> + <translation type="obsolete">Dirección</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="176"/> + <source>Status</source> + <translation type="obsolete">Estado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="177"/> + <source>Hostname</source> + <translation type="obsolete">Hostname</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="183"/> + <source>Version</source> + <translation type="obsolete">Versión</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="180"/> + <source>Rules</source> + <translation type="obsolete">Reglas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Time</source> + <translation type="obsolete">Hora</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Action</source> + <translation type="obsolete">Acción</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Duration</source> + <translation type="obsolete">Duración</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Node</source> + <translation type="obsolete">Nodo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="253"/> + <source>Hits</source> + <translation>Total</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>Protocol</source> + <translation type="obsolete">Protocolo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1837"/> + <source>Save as CSV</source> + <translation>Guardar como CSV</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>Enabled</source> + <translation type="obsolete">Habilitado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="738"/> + <source>Delete</source> + <translation>Eliminar</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="948"/> + <source>always</source> + <translation type="obsolete">siempre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="580"/> + <source><b>Error:</b><br><br>{0}</source> + <translation type="obsolete"><b>Error:</b><br><br>{0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="731"/> + <source>Disable</source> + <translation>Deshabilitar</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="733"/> + <source>Enable</source> + <translation>Habilitar</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="736"/> + <source>Duplicate</source> + <translation>Duplicar</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="737"/> + <source>Edit</source> + <translation>Editar</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="891"/> + <source>Rule not found by that name and node</source> + <translation>Regla no encontrada por ese nombre o nodo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="921"/> + <source><b>Error:</b><br><br></source> + <comment>{0}</comment> + <translation><b>Error:</b><br><br></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="928"/> + <source>Warning:</source> + <translation>Aviso:</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="717"/> + <source>Allow</source> + <translation>Permitir</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="718"/> + <source>Deny</source> + <translation>Denegar</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="722"/> + <source>Always</source> + <translation>Siempre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="723"/> + <source>Until reboot</source> + <translation>Hasta reiniciar</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1244"/> + <source> You are about to delete this rule. </source> + <translation> Estás a punto de borrar esta regla. </translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="306"/> + <source>Process</source> + <translation type="obsolete">Aplicación</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="307"/> + <source>Destination</source> + <translation type="obsolete">Destino</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="308"/> + <source>Rule</source> + <translation type="obsolete">Regla</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="309"/> + <source>UserID</source> + <translation type="obsolete">UserID</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="174"/> + <source>LastConnection</source> + <translation type="obsolete">ÚltimaConexión</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <comment>xxxxx</comment> + <translation type="obsolete">Nombre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Nombre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="294"/> + <source>Address</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Dirección</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="295"/> + <source>Status</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Estado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="296"/> + <source>Hostname</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Hostname</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="297"/> + <source>Version</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Versión</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="298"/> + <source>Rules</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Reglas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Time</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Hora</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Action</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Acción</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Duration</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Duración</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Node</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Nodo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>Enabled</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Habilitado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="304"/> + <source>Hits</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Total</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>Protocol</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Protocolo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="306"/> + <source>Process</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Aplicación</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="307"/> + <source>Destination</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Destino</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="308"/> + <source>Rule</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Regla</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="309"/> + <source>UserID</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">UserID</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="310"/> + <source>LastConnection</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">ÚltimaConexión</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="392"/> + <source>Name</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Nombre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="288"/> + <source>Address</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Dirección</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="379"/> + <source>Status</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Estado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="380"/> + <source>Hostname</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Hostname</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="386"/> + <source>Version</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Versión</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="383"/> + <source>Rules</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Reglas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="390"/> + <source>Time</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Hora</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="395"/> + <source>Action</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Acción</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="396"/> + <source>Duration</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Duración</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="391"/> + <source>Node</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Nodo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="393"/> + <source>Enabled</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Habilitado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="405"/> + <source>Hits</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Total</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Protocol</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Protocolo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Process</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Aplicación</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Destination</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Destino</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="306"/> + <source>Rule</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Regla</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="307"/> + <source>UserID</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>UserID</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="377"/> + <source>LastConnection</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>ÚltimaConexión</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Args</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Args</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>DstIP</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>IPDestino</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="304"/> + <source>DstHost</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>HostDestino</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>DstPort</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>PuertoDestino</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="175"/> + <source>Addr</source> + <translation type="obsolete">Dirección</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="181"/> + <source>Connections</source> + <translation type="obsolete">Conexiones</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="182"/> + <source>Dropped</source> + <translation type="obsolete">Rechazadas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="252"/> + <source>What</source> + <translation>Qué</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="709"/> + <source>Apply to</source> + <translation>Aplicar a</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="719"/> + <source>Reject</source> + <translation>Rechazar</translation> + </message> + <message> + <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="25"/> + <source>Network name</source> + <translation>Red</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="378"/> + <source>Addr</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Dirección</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="382"/> + <source>Uptime</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Tiempo de ejecución</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="384"/> + <source>Connections</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Conexiones</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="385"/> + <source>Dropped</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Rechazadas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="404"/> + <source>What</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Qué</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="394"/> + <source>Precedence</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Prioritaria</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="644"/> + <source>New node connected</source> + <translation>Nuevo nodo conectado</translation> + </message> +</context> +<context> + <name>stats_deleterule</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="774"/> + <source> Your are about to delete this rule. </source> + <translation type="obsolete"> Estás a punto de borrar esta regla. </translation> + </message> +</context> +<context> + <name>stats_deleterule2</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="776"/> + <source> Are you sure?</source> + <translation type="obsolete"> ¿Estás seguro?</translation> + </message> +</context> +<context> + <name>stats_disabled</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="74"/> + <source>Disabled</source> + <translation type="obsolete">Deshabilitado</translation> + </message> +</context> +<context> + <name>stats_notrunning</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="73"/> + <source>Not running</source> + <translation type="obsolete">Parado</translation> + </message> +</context> +<context> + <name>stats_running</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="75"/> + <source>Running</source> + <translation type="obsolete">Interceptando</translation> + </message> +</context> +<context> + <name>stats_wintitle</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="409"/> + <source>OpenSnitch Network Statistics</source> + <translation type="obsolete">Eventos de red OpenSnitch</translation> + </message> +</context> +<context> + <name>stats_wintitle2</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="411"/> + <source>OpenSnitch Network Statistics for</source> + <translation type="obsolete">Eventos de OpenSnitch de</translation> + </message> +</context> +</TS> diff --git a/ui/i18n/locales/eu_ES/opensnitch-eu_ES.ts b/ui/i18n/locales/eu_ES/opensnitch-eu_ES.ts new file mode 100644 index 0000000..5153cba --- /dev/null +++ b/ui/i18n/locales/eu_ES/opensnitch-eu_ES.ts @@ -0,0 +1,1878 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS><TS version="2.0"> +<context> + <name>Dialog</name> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="34"/> + <source>opensnitch-qt</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="299"/> + <source>User ID</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="333"/> + <source><html><head/><body><p><span style=" font-weight:600;">Executed from</span></p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="630"/> + <source>TextLabel</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="426"/> + <source>Source IP</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="449"/> + <source>Process ID</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="582"/> + <source>Destination IP</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="605"/> + <source>Dst Port</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="679"/> + <source>from this executable</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="684"/> + <source>from this command line</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="689"/> + <source>this destination port</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="694"/> + <source>this user</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="699"/> + <source>this destination ip</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="728"/> + <source>once</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="733"/> + <source>30s</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="738"/> + <source>5m</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="743"/> + <source>15m</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="748"/> + <source>30m</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="753"/> + <source>1h</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="763"/> + <source>forever</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="789"/> + <source>Deny</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="818"/> + <source>Allow</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="847"/> + <source>+</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="758"/> + <source>until reboot</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="704"/> + <source>from this PID</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>New node connected</name> +</context> +<context> + <name>PreferencesDialog</name> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="14"/> + <source>Preferences</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="472"/> + <source>UI</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="454"/> + <source>Default timeout</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="331"/> + <source>Pop-up default duration</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="754"/> + <source>Default duration</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="314"/> + <source>Default target</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="170"/> + <source>center</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="175"/> + <source>top right</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="180"/> + <source>bottom right</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="185"/> + <source>top left</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="190"/> + <source>bottom left</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="274"/> + <source>by executable</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="279"/> + <source>by command line</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="284"/> + <source>by destination port</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="289"/> + <source>by destination ip</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="294"/> + <source>by user id</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="863"/> + <source>once</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="210"/> + <source>30s</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="215"/> + <source>5m</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="220"/> + <source>15m</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="225"/> + <source>30m</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="230"/> + <source>1h</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="240"/> + <source>forever</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="905"/> + <source>deny</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="914"/> + <source>allow</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="711"/> + <source>Nodes</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="717"/> + <source>Process monitor method</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="751"/> + <source><html><head/><body><p>The default duration will take place when there's no UI connected.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="881"/> + <source><html><head/><body><p>Address of the node.</p><p>Default: unix:///tmp/osui.sock (unix:// is mandatory if it's a Unix socket)</p><p>It can also be an IP address with the port: 127.0.0.1:50051</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="884"/> + <source>Address</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1024"/> + <source>Default log level</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="932"/> + <source>Version</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="790"/> + <source><html><head/><body><p>The default action will take place when there's no UI connected.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="734"/> + <source><html><head/><body><p>Log file to write logs.<br/></p><p>/dev/stdout will print logs to the standard output.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="737"/> + <source>Log file</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="809"/> + <source>HostName</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="983"/> + <source>unix:///tmp/osui.sock</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="868"/> + <source>until restart</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="873"/> + <source>always</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="995"/> + <source>/var/log/opensnitchd.log</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1000"/> + <source>/dev/stdout</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="767"/> + <source>Apply configuration to all nodes</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1039"/> + <source>Database</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1074"/> + <source>In memory</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1079"/> + <source>File</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1345"/> + <source>Close</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1356"/> + <source>Apply</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1367"/> + <source>Save</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="235"/> + <source>until reboot</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="350"/> + <source>Show advanced view by default</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="665"/> + <source>Action</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="366"/> + <source><html><head/><body><p>If checked, the pop-ups will be displayed with the advanced view active.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="334"/> + <source>Duration</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="254"/> + <source><html><head/><body><p>By default when a new pop-up appears, in its simplest form, you'll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).</p><p>With these options, you can choose multiple fields to filter connections for.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="257"/> + <source>Filter connections also by:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="81"/> + <source>User ID</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="97"/> + <source>Destination port</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="113"/> + <source>Destination IP</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="451"/> + <source><html><head/><body><p>This timeout is the countdown you see when a pop-up dialog is shown.</p><p>If the pop-up is not answered, the default options will be applied.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1093"/> + <source>Database type</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1100"/> + <source>Select</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="347"/> + <source>The advanced view allows you to easily select multiple fields to filter connections</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="110"/> + <source>If checked, this field will be selected when a pop-up is displayed</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="150"/> + <source><html><head/><body><p>Pop-up default action.</p><p>When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.</p><p><br/></p><p>While a pop-up is asking the user to allow or deny a connection:</p><p>1. new outgoing connections are denied.</p><p>2. known connections are allowed or denied based on the rules defined by the user.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="793"/> + <source>Default action when the GUI is disconnected</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="894"/> + <source>Debug invalid connections</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="39"/> + <source>Pop-ups</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="64"/> + <source>Default options</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="321"/> + <source>Default position on screen</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="479"/> + <source>any temporary rules</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="492"/> + <source><html><head/><body><p>When this option is selected, the rules of the selected duration won't be added to the list of temporary rules in the GUI.</p><p><br/></p><p>Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="495"/> + <source>Don't save rules of duration</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="601"/> + <source>Time</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="681"/> + <source>Destination</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="649"/> + <source>Protocol</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="697"/> + <source>Process</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="617"/> + <source>Rule</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="633"/> + <source>Node</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="589"/> + <source>Events tab columns</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="299"/> + <source>by PID</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="461"/> + <source>Disable pop-ups, only display an notification</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="508"/> + <source>Desktop notifications</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="526"/> + <source>Use system notifications</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="542"/> + <source>Use Qt notifications</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="571"/> + <source>Test</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="891"/> + <source><html><head/><body><p>If checked, OpenSnitch will prompt you to allow or deny connections that don't have an associated PID, due to several reasons, mostly due to bad state connections.</p><p>The pop-up dialog will only contain information about the network connection.</p><p>There're some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1178"/> + <source>minutes</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1204"/> + <source>Minutes between events purges</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1227"/> + <source>days</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1237"/> + <source>Maximum days of events to keep</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>ProcessDetailsDialog</name> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="14"/> + <source>Process details</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="61"/> + <source>loading...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="81"/> + <source>CWD: loading...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="93"/> + <source>mem stats: loading...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="121"/> + <source>Status</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="135"/> + <source>Open files</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="149"/> + <source>I/O Statistics</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="163"/> + <source>Memory mapped files</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="177"/> + <source>Stack</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="191"/> + <source>Environment variables</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="210"/> + <source>Application pids</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="240"/> + <source>Start or stop monitoring this process</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="256"/> + <source>Close</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>RulesDialog</name> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="14"/> + <source>Rule</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="118"/> + <source>Node</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="141"/> + <source>Apply rule to all nodes</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="239"/> + <source>From this command line</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="206"/> + <source>From this executable</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="751"/> + <source>Action</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="383"/> + <source>To this IP / Network</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="792"/> + <source>once</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="797"/> + <source>30s</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="802"/> + <source>5m</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="807"/> + <source>15m</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="812"/> + <source>30m</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="817"/> + <source>1h</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="827"/> + <source>always</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="293"/> + <source>To this port</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="169"/> + <source>From this user ID</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="419"/> + <source>Commas or spaces are not allowed to specify multiple domains. + +Use regular expressions instead: +.*(opensnitch|duckduckgo).com +.*\.google.com + +or a single domain: +www.gnu.org - it'll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ... +gnu.org - it'll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="430"/> + <source>www.domain.org, .*\.domain.org</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="323"/> + <source><html><head/><body><p>Only TCP, UDP or UDPLITE are allowed</p><p>You can use regexp, i.e.: ^(TCP|UDP)$</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="333"/> + <source>TCP</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="338"/> + <source>UDP</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="343"/> + <source>UDPLITE</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="348"/> + <source>TCP6</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="353"/> + <source>UDP6</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="358"/> + <source>UDPLITE6</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="440"/> + <source>You can specify a single IP: +- 192.168.1.1 + +or a regular expression: +- 192\.168\.1\.[0-9]+ + +multiple IPs: +- ^(192\.168\.1\.1|172\.16\.0\.1)$ + +You can also specify a subnet: +- 192.168.1.0/24 + +Note: Commas or spaces are not allowed to separate IPs or networks.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="459"/> + <source>LAN</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="464"/> + <source>127.0.0.0/8</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="469"/> + <source>192.168.0.0/24</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="474"/> + <source>192.168.1.0/24</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="479"/> + <source>192.168.2.0/24</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="484"/> + <source>192.168.0.0/16</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="489"/> + <source>169.254.0.0/16</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="494"/> + <source>172.16.0.0/12</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="499"/> + <source>10.0.0.0/8</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="504"/> + <source>::1/128</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="509"/> + <source>fc00::/7</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="514"/> + <source>ff00::/8</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="519"/> + <source>fe80::/10</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="524"/> + <source>fd00::/8</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="784"/> + <source>Duration</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="376"/> + <source>Protocol</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="300"/> + <source>To this host</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="843"/> + <source>Deny</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="877"/> + <source>Allow</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="899"/> + <source>Name</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="22"/> + <source>Enable</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="706"/> + <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them. + +000-allow-localhost +001-deny-broadcast +...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="713"/> + <source>leave blank to autocreate</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="46"/> + <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one. + +You must name the rule in such manner that it'll be checked first, because they're checked in alphabetical order. For example: + +[x] Priority - 000-priority-rule +[ ] Priority - 001-less-priority-rule</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="54"/> + <source>Priority rule</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="74"/> + <source><html><head/><body><p>By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.<br/></p><p>If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="77"/> + <source>Case-sensitive</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="369"/> + <source><html><head/><body><p>You can specify multiple ports using regular expressions:</p><p><br/></p><p>- 53, 80 or 443:</p><p>^(53|80|443)$</p><p><br/></p><p>- 53, 443 or 5551, 5552, 5553, etc:</p><p>^(53|443|555[0-9])$</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="822"/> + <source>until reboot</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="583"/> + <source>To this list of domains</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="163"/> + <source>Applications</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="216"/> + <source><html><head/><body><p>This field will only match the executable path. It is not modifiable by the user.<br/></p><p>You can use regular expressions to deny executions from /tmp for example:<br/></p><p>^/tmp/.*$</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="229"/> + <source><html><head/><body><p>This field will contain and match the command line that was executed by the user.<br/></p><p>If the user typed the command, only the command will appear:</p><p>telnet 1.2.3.4<br/></p><p>If the user typed the absolute or relative path to the command, that is what will appear:</p><p>/usr/bin/telnet 1.2.3.4</p><p>../../../usr/bin/telnet 1.2.3.4</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="246"/> + <source>From this PID</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="287"/> + <source>Network</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="536"/> + <source>List of domains/IPs</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="542"/> + <source>To this list of network ranges</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="549"/> + <source>To this list of IPs</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="574"/> + <source><html><head/><body><p>Select a directory with files containing list of IPs to block or allow:</p><p>1.2.3.4.5</p><p>1.2.3.4.6</p><p>.</p><p>etc.</p><p>One IP per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="608"/> + <source><html><head/><body><p>Select a directory with files containing list of network ranges to block or allow:</p><p>1.2.3.0/24</p><p>80.34.56.0/20</p><p>.</p><p>etc.<br/></p><p>One Network Range per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="636"/> + <source><html><head/><body><p>Select a directory with lists of domains to block or allow.</p><p>Put inside that directory files with any extension containing lists of domains.</p><p><br/>The format of each entry of a list is as follow (hosts format):</p><p>127.0.0.1 www.domain.com</p><p>or </p><p>0.0.0.0 www.domain.com</p><p>Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="651"/> + <source>To this list of domains +(regular expressions)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="677"/> + <source><html><head/><body><p>Select a directory with files containing regular expressions of domains to block or allow:</p><p>.*\.example\.com</p><p>You can also use a domain as is: &quot;example.com&quot; , and it'll match whatever.example.com, whatever.example.com.localdomain, etc.</p><p>One domain per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="857"/> + <source>Reject</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>StatsDialog</name> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="34"/> + <source>OpenSnitch Network Statistics</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="290"/> + <source>Save to CSV.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="300"/> + <source>Ctrl+S</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="351"/> + <source>Create a new rule</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="381"/> + <source><html><head/><body><p><span style=" font-size:11pt; font-weight:600;">hostname - 192.168.1.1</span></p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="420"/> + <source>Status</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1665"/> + <source>-</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="464"/> + <source>Start or Stop interception</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="509"/> + <source>Events</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="94"/> + <source>Filter</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="107"/> + <source>Allow</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="116"/> + <source>Deny</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="143"/> + <source>Ex.: firefox</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="199"/> + <source>50</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="204"/> + <source>100</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="209"/> + <source>200</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="214"/> + <source>300</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="748"/> + <source>Nodes</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1569"/> + <source>Rules</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="857"/> + <source>enable</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="704"/> + <source>Application rules</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="806"/> + <source>Permanent</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="815"/> + <source>Temporary</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="933"/> + <source>Hosts</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1020"/> + <source>Applications</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1127"/> + <source>Addresses</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1214"/> + <source>Ports</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1298"/> + <source>Users</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1404"/> + <source>Connections</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1459"/> + <source>Dropped</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1514"/> + <source>Uptime</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1639"/> + <source>Version</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="227"/> + <source>Delete all intercepted events</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="864"/> + <source>Edit rule</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="878"/> + <source>Delete rule</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="912"/> + <source>Delete connections that matched this rule</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="797"/> + <source>All applications</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="125"/> + <source>Reject</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="177"/> + <source>0</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>contextual_menu</name> + <message> + <location filename="../../../opensnitch/service.py" line="43"/> + <source>Statistics</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="46"/> + <source>Help</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="47"/> + <source>Close</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="44"/> + <source>Enable</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="45"/> + <source>Disable</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>notifications</name> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="518"/> + <source>System notifications are not available, you need to install python3-notify2.</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>popups</name> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="52"/> + <source>until reboot</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="54"/> + <source>forever</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="91"/> + <source>Allow</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="92"/> + <source>Deny</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="285"/> + <source>Outgoing connection</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="290"/> + <source>Process launched from:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="321"/> + <source>from this executable</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="325"/> + <source>from this command line</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="327"/> + <source>to port {0}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="390"/> + <source>to {0}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="330"/> + <source>from user {0}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="347"/> + <source>to {0}.*</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="400"/> + <source>to *.{0}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="434"/> + <source><b>Remote</b> process %s running on <b>%s</b></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="438"/> + <source>is connecting to <b>%s</b> on %s port %d</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="444"/> + <source>is attempting to resolve <b>%s</b> via %s, %s port %d</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="334"/> + <source>from this PID</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/notifications.py" line="105"/> + <source>New outgoing connection</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>preferences</name> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="208"/> + <source>Server address can not be empty</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="448"/> + <source>Configuration applied.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="299"/> + <source>Exception saving config: {0}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="389"/> + <source>Applying configuration on {0} ...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="238"/> + <source>Error loading {0} configuration</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="450"/> + <source>Error applying configuration: {0}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="323"/> + <source>Warning</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="323"/> + <source>You must select a file for the database<br>or choose "In memory" type.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="329"/> + <source>DB type changed</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="329"/> + <source>Restart the GUI in order effects to take effect</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="480"/> + <source>Hover the mouse over the texts to display the help<br><br>Don't forget to visit the wiki: <a href="{0}">{0}</a></source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>proc_details</name> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="97"/> + <source><b>Error loading process information:</b> <br><br> + +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="116"/> + <source><b>Error stopping monitoring process:</b><br><br></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="156"/> + <source>loading...</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>rules</name> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="162"/> + <source>There're no nodes connected.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="203"/> + <source>Rule applied.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="527"/> + <source>protocol can not be empty, or uncheck it</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="541"/> + <source>Protocol regexp error</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="545"/> + <source>process path can not be empty</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="559"/> + <source>Process path regexp error</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="563"/> + <source>command line can not be empty</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="577"/> + <source>Command line regexp error</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="581"/> + <source>Dest port can not be empty</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="595"/> + <source>Dst port regexp error</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="599"/> + <source>Dest host can not be empty</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="613"/> + <source>Dst host regexp error</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="617"/> + <source>Dest IP/Network can not be empty</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="639"/> + <source>Dst IP regexp error</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="651"/> + <source>User ID can not be empty</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="665"/> + <source>User ID regexp error</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="205"/> + <source>Error applying rule: {0}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="429"/> + <source><b>Error loading rule</b></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="739"/> + <source>Lists field cannot be empty</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="741"/> + <source>Lists field must be a directory</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="777"/> + <source><b>Rule not supported</b></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="179"/> + <source>There's already a rule with this name.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="669"/> + <source>PID field can not be empty</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="683"/> + <source>PID field regexp error</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="764"/> + <source>Select at least one field.</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>stats</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="310"/> + <source>Not running</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="311"/> + <source>Disabled</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="312"/> + <source>Running</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="761"/> + <source> Your are about to delete this rule. </source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1244"/> + <source> Are you sure?</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="568"/> + <source>OpenSnitch Network Statistics {0}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="570"/> + <source>OpenSnitch Network Statistics for {0}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1837"/> + <source>Save as CSV</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="738"/> + <source>Delete</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="921"/> + <source><b>Error:</b><br><br></source> + <comment>{0}</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="928"/> + <source>Warning:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="717"/> + <source>Allow</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="718"/> + <source>Deny</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="722"/> + <source>Always</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="723"/> + <source>Until reboot</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="731"/> + <source>Disable</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="733"/> + <source>Enable</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="736"/> + <source>Duplicate</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="737"/> + <source>Edit</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="891"/> + <source>Rule not found by that name and node</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1244"/> + <source> You are about to delete this rule. </source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="392"/> + <source>Name</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="288"/> + <source>Address</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="379"/> + <source>Status</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="380"/> + <source>Hostname</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="386"/> + <source>Version</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="383"/> + <source>Rules</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="390"/> + <source>Time</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="395"/> + <source>Action</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="396"/> + <source>Duration</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="391"/> + <source>Node</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="393"/> + <source>Enabled</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="405"/> + <source>Hits</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Protocol</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Process</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Destination</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="306"/> + <source>Rule</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="307"/> + <source>UserID</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="377"/> + <source>LastConnection</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Args</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>DstIP</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="304"/> + <source>DstHost</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>DstPort</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="252"/> + <source>What</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="253"/> + <source>Hits</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="709"/> + <source>Apply to</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="719"/> + <source>Reject</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="25"/> + <source>Network name</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="378"/> + <source>Addr</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="382"/> + <source>Uptime</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="384"/> + <source>Connections</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="385"/> + <source>Dropped</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="404"/> + <source>What</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="394"/> + <source>Precedence</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="644"/> + <source>New node connected</source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> diff --git a/ui/i18n/locales/fr_FR/opensnitch-fr_FR.ts b/ui/i18n/locales/fr_FR/opensnitch-fr_FR.ts new file mode 100644 index 0000000..0d5f7f3 --- /dev/null +++ b/ui/i18n/locales/fr_FR/opensnitch-fr_FR.ts @@ -0,0 +1,2420 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS><TS version="2.0" language="fr" sourcelanguage=""> +<context> + <name>Dialog</name> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="34"/> + <source>opensnitch-qt</source> + <translation>opensnitch-qt</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="299"/> + <source>User ID</source> + <translation>ID utilisateur</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="333"/> + <source><html><head/><body><p><span style=" font-weight:600;">Executed from</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-weight:600;">Exécuté depuis</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="630"/> + <source>TextLabel</source> + <translation>TextLabel</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="426"/> + <source>Source IP</source> + <translation>IP source</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="449"/> + <source>Process ID</source> + <translation>ID processus</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="582"/> + <source>Destination IP</source> + <translation>IP destination</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="605"/> + <source>Dst Port</source> + <translation>interface destination</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="679"/> + <source>from this executable</source> + <translation>depuis cet exécutable</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="684"/> + <source>from this command line</source> + <translation>depuis cette ligne de commande</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="689"/> + <source>this destination port</source> + <translation>vers cette interface</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="694"/> + <source>this user</source> + <translation>cet utilisateur</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="699"/> + <source>this destination ip</source> + <translation>vers cette IP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="728"/> + <source>once</source> + <translation>une seule fois</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="733"/> + <source>30s</source> + <translation>30s</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="738"/> + <source>5m</source> + <translation>5mn</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="743"/> + <source>15m</source> + <translation>15mn</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="748"/> + <source>30m</source> + <translation>30mn</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="753"/> + <source>1h</source> + <translation>1h</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="706"/> + <source>for this session</source> + <translation type="obsolete">durante esta sesión</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="763"/> + <source>forever</source> + <translation>définitivement</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="789"/> + <source>Deny</source> + <translation>Refuser</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="818"/> + <source>Allow</source> + <translation>Permettre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="847"/> + <source>+</source> + <translation>+</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="758"/> + <source>until reboot</source> + <translation>jusqu'au redémarrage</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="704"/> + <source>from this PID</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>New node connected</name> +</context> +<context> + <name>PreferencesDialog</name> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="14"/> + <source>Preferences</source> + <translation>Préférences</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="472"/> + <source>UI</source> + <translation>IU</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="54"/> + <source><html><head/><body><p>This timeout is the countdown you see when a pop-up dialog is shown.</p></body></html></source> + <translation type="obsolete">Este timeout es la cuenta atrás que aparece cuando se muestra una ventana emergente</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="454"/> + <source>Default timeout</source> + <translation>attente par défaut</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="331"/> + <source>Pop-up default duration</source> + <translation>attente dialogue par défaut</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="754"/> + <source>Default duration</source> + <translation>attente par défaut</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="162"/> + <source>Pop-up default action</source> + <translation type="obsolete">Acción por defecto de la ventana emergente</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="483"/> + <source>Default action</source> + <translation type="obsolete">Acción por defecto</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="314"/> + <source>Default target</source> + <translation>cible par défaut</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="170"/> + <source>center</source> + <translation>centré</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="175"/> + <source>top right</source> + <translation>en haut à droite</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="180"/> + <source>bottom right</source> + <translation>en bas à gauche</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="185"/> + <source>top left</source> + <translation>en haut à gauche</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="190"/> + <source>bottom left</source> + <translation>en bas à gauche</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="167"/> + <source>Prompt dialog default position on screen</source> + <translation type="obsolete">Posición por defecto</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="274"/> + <source>by executable</source> + <translation>par l'exécutable</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="279"/> + <source>by command line</source> + <translation>par la ligne de commande</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="284"/> + <source>by destination port</source> + <translation>par l'interface de destination</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="289"/> + <source>by destination ip</source> + <translation>par l'IP de destination</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="294"/> + <source>by user id</source> + <translation>par cet utilisateur</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="863"/> + <source>once</source> + <translation>une seule fois</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="210"/> + <source>30s</source> + <translation>30s</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="215"/> + <source>5m</source> + <translation>5m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="220"/> + <source>15m</source> + <translation>15m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="225"/> + <source>30m</source> + <translation>30m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="230"/> + <source>1h</source> + <translation>1h</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="240"/> + <source>for this session</source> + <translation type="obsolete">durante esta sesión</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="240"/> + <source>forever</source> + <translation>définitivement</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="905"/> + <source>deny</source> + <translation>Refuser</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="914"/> + <source>allow</source> + <translation>Autoriser</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="406"/> + <source>Disable pop-ups, only display an alert</source> + <translation type="obsolete">Pas de dialogue, montrer juste une alerte</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="711"/> + <source>Nodes</source> + <translation>noeuds</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="717"/> + <source>Process monitor method</source> + <translation>méthode de surveillance des processus</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="751"/> + <source><html><head/><body><p>The default duration will take place when there's no UI connected.</p></body></html></source> + <translation><html><head/><body><p>La durée par défaut concerne le cas où aucun utilisateur n'est connecté.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="881"/> + <source><html><head/><body><p>Address of the node.</p><p>Default: unix:///tmp/osui.sock (unix:// is mandatory if it's a Unix socket)</p><p>It can also be an IP address with the port: 127.0.0.1:50051</p></body></html></source> + <translation><html><head/><body><p>Adresse du noeud.</p><p>Default: unix:///tmp/osui.sock (unix:// requise si c'est un socket Unix)</p><p>Peut aussi être une adresse IP avec son port: 127.0.0.1:50051</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="884"/> + <source>Address</source> + <translation>Adresse</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1024"/> + <source>Default log level</source> + <translation>détail noté dans le log par défaut</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="932"/> + <source>Version</source> + <translation>Version</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="790"/> + <source><html><head/><body><p>The default action will take place when there's no UI connected.</p></body></html></source> + <translation><html><head/><body><p>L'action par défaut se déclenche s'il n'y a pas d'utilisateur connecté.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="734"/> + <source><html><head/><body><p>Log file to write logs.<br/></p><p>/dev/stdout will print logs to the standard output.</p></body></html></source> + <translation><html><head/><body><p>fichier dans lequel enregistrer le log<br/></p><p>/dev/stdout imprime les logs dans le standard output.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="737"/> + <source>Log file</source> + <translation>Fichier log</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="578"/> + <source><html><head/><body><p>If checked, opensnitch will prompt you to allow or deny connections that don't have an asocciated PID, due to several reasons.</p><p>The pop-up dialog will only contain information about the network connection.</p></body></html></source> + <translation type="obsolete">Si marcas esta opción, OpenSnitch te preguntará para Aceptar o Denegar conexiones que no tengan un PID asociado por diferentes razones. + +La ventana emergente sólo contendrá información relativa a la conexión. + +Nota: Estas conexiones no tienen por qué indicar que algo sospechoso está sucediendo. Simplemente +es que no hemos descubierto el PID (por ejemplo conexiones que no se originan en la máquina, o paquetes en mal estado).</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="581"/> + <source>Intercept Unknown Connections</source> + <translation type="obsolete">Interceptar conexiones desconocidas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="809"/> + <source>HostName</source> + <translation>nom d'hôte (host)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="983"/> + <source>unix:///tmp/osui.sock</source> + <translation>unix:///tmp/osui.sock</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="868"/> + <source>until restart</source> + <translation>jusqu'au redémarrage</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="873"/> + <source>always</source> + <translation>toujours</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="995"/> + <source>/var/log/opensnitchd.log</source> + <translation>/var/log/opensnitchd.log</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1000"/> + <source>/dev/stdout</source> + <translation>/dev/stdout</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="767"/> + <source>Apply configuration to all nodes</source> + <translation>appliquer la configuration à tous les noeuds</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1039"/> + <source>Database</source> + <translation>Base de données</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1074"/> + <source>In memory</source> + <translation>En mémoire</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1079"/> + <source>File</source> + <translation>Fichier</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1345"/> + <source>Close</source> + <translation>Fermer</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1356"/> + <source>Apply</source> + <translation>Appliquer</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1367"/> + <source>Save</source> + <translation>Enregistrer</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="235"/> + <source>until reboot</source> + <translation>jusqu'au redémarrage</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1093"/> + <source>Database type</source> + <translation>type de base de données</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1100"/> + <source>Select</source> + <translation>Choisir</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="83"/> + <source>Pop-ups default options</source> + <translation type="obsolete">Opciones por defecto de las ventanas emergentes</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="367"/> + <source>Pop-ups default position on screen</source> + <translation type="obsolete">Posición en pantalla</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="102"/> + <source><html><head/><body><p>The advanced view allows you to apply more filters on a connection</p><p>when a pop-up appears.</p></body></html></source> + <translation type="obsolete">La vista avanzada permite filtrar conexiones por más parámetros</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="350"/> + <source>Show advanced view by default</source> + <translation>Montrer par défaut la vue détaillée</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="665"/> + <source>Action</source> + <translation>Action</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="366"/> + <source><html><head/><body><p>If checked, the pop-ups will be displayed with the advanced view active.</p></body></html></source> + <translation><html><head/><body><p>Sélectionner pour que les dialogues s'ouvrent avec le détail avancé.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="334"/> + <source>Duration</source> + <translation>Durée</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="254"/> + <source><html><head/><body><p>By default when a new pop-up appears, in its simplest form, you'll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).</p><p>With these options, you can choose multiple fields to filter connections for.</p></body></html></source> + <translation><html><head/><body><p>Par défaut un nouveau dialogue en version simple propose de filtrer les connexions ou les applications sur une propriété de la connexion (exécutable, port, IP, etc).</p><p>Avec ces options, vous pouvez choisir plusieurs critères pour filtrer les connexions.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="257"/> + <source>Filter connections also by:</source> + <translation>Filtrer aussi les connexion par :</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="362"/> + <source>If checked, this field will be checked when a pop-up is displayed</source> + <translation type="obsolete">Si lo seleccionas, este campo se usará para filtrar las conexiones</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="81"/> + <source>User ID</source> + <translation>ID utilisateur</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="97"/> + <source>Destination port</source> + <translation>interface (port) de destination</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="113"/> + <source>Destination IP</source> + <translation>IP de destination</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="451"/> + <source><html><head/><body><p>This timeout is the countdown you see when a pop-up dialog is shown.</p><p>If the pop-up is not answered, the default options will be applied.</p></body></html></source> + <translation><html><head/><body><p>Ctte durée est l'attente par défaut lorsqu'un dialogue est présenté.</p><p>Sans réponse au dialogue, les options par défaut sont appliquées.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="347"/> + <source>The advanced view allows you to easily select multiple fields to filter connections</source> + <translation>La vue avancée permet de choisir facileent plusieurs critères pour filtrer les connexions</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="110"/> + <source>If checked, this field will be selected when a pop-up is displayed</source> + <translation>cocher pour que ce champ soit préselectionné lorsqu'un dialogue est affiché</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="150"/> + <source><html><head/><body><p>Pop-up default action.</p><p>When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.</p><p><br/></p><p>While a pop-up is asking the user to allow or deny a connection:</p><p>1. new outgoing connections are denied.</p><p>2. known connections are allowed or denied based on the rules defined by the user.</p></body></html></source> + <translation><html><head/><body><p>Action par défaut du dialogue.</p><p>Lorsqu'un nouveau dialogue est affiché, cette action sera présélectionnée, et donc appliquée s'il n'y a pas de réponse après le délai par défaut.</p><p><br/></p><p>Pendant que le dialogue demande si on autorise ou non la connexion:</p><p>1. toute autre nouvelle demande de connexion est refusée.</p><p>2. les connexions déjà connues sont autorisée ou rejetées selon les règles définies par l'utilisateur.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="793"/> + <source>Default action when the GUI is disconnected</source> + <translation>Action par défaut lorsque l'interface graphique utilisateur n'est pas connectée</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="894"/> + <source>Debug invalid connections</source> + <translation>Noter (debug) les connexions invalides</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="39"/> + <source>Pop-ups</source> + <translation>Dialogues</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="64"/> + <source>Default options</source> + <translation>Options par défaut</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="321"/> + <source>Default position on screen</source> + <translation>Position par défaut sur l'écran</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="479"/> + <source>any temporary rules</source> + <translation>toute règle temporaire</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="492"/> + <source><html><head/><body><p>When this option is selected, the rules of the selected duration won't be added to the list of temporary rules in the GUI.</p><p><br/></p><p>Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</p></body></html></source> + <translation><html><head/><body><p>Lorsque cette option est choisie, les règles de la durée sélectionnée ne sont pas ajoutées à la liste des règles temporaires présentées dans l'interface graphique utilisateur.</p><p><br/></p><p>Les règles temporaires sont cependant toujours valides et peuvent être utilisées lors d'une demande d'autorisation /refus de connexion.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="495"/> + <source>Don't save rules of duration</source> + <translation>Ne pas enregistrer les règles de cette durée :</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="463"/> + <source>Show events columns</source> + <translation type="obsolete">Mostrar columnas de la pestaña Eventos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="601"/> + <source>Time</source> + <translation>Temps</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="681"/> + <source>Destination</source> + <translation>Destination</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="649"/> + <source>Protocol</source> + <translation>Protocole</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="697"/> + <source>Process</source> + <translation>Processus</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="617"/> + <source>Rule</source> + <translation>Règle</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="633"/> + <source>Node</source> + <translation>Noeud</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="723"/> + <source><html><head/><body><p>If checked, opensnitch will prompt you to allow or deny connections that don't have an asocciated PID, due to several reasons, mostly due to bad state connections.</p><p>The pop-up dialog will only contain information about the network connection.</p><p>There're some scenarios where these are valid connections though, like when establishing a VPN using wireguard.</p></body></html></source> + <translation type="obsolete"><html><head/><body><p>cochez ceci pour recevoir une demande d'autorisation/refus sur les connexions qui n'ont pas de processus (PID) associé, généralement parce que la connexion est en mauvais état.</p><p>Le dialogue ne contiendra alors que l'information sur la connexion.</p><p>Il y a cependant des scénarios pour lesquels ces demandes sont valides, comme par exemple l'établissement d'un VPN utilisant wireguard.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="589"/> + <source>Events tab columns</source> + <translation>colonnes évènements</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="299"/> + <source>by PID</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="461"/> + <source>Disable pop-ups, only display an notification</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="508"/> + <source>Desktop notifications</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="526"/> + <source>Use system notifications</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="542"/> + <source>Use Qt notifications</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="571"/> + <source>Test</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="891"/> + <source><html><head/><body><p>If checked, OpenSnitch will prompt you to allow or deny connections that don't have an associated PID, due to several reasons, mostly due to bad state connections.</p><p>The pop-up dialog will only contain information about the network connection.</p><p>There're some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1178"/> + <source>minutes</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1204"/> + <source>Minutes between events purges</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1227"/> + <source>days</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1237"/> + <source>Maximum days of events to keep</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>ProcessDetailsDialog</name> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="14"/> + <source>Process details</source> + <translation>Détail processus</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="61"/> + <source>loading...</source> + <translation>chargement...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="81"/> + <source>CWD: loading...</source> + <translation>chargement CWD...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="93"/> + <source>mem stats: loading...</source> + <translation>chargement statistiques mémoire...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="121"/> + <source>Status</source> + <translation>Etat</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="135"/> + <source>Open files</source> + <translation>Fichiers ouverts</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="149"/> + <source>I/O Statistics</source> + <translation>Statistiques entrées/sorties</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="163"/> + <source>Memory mapped files</source> + <translation>Fichiers mappés en mémoire</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="177"/> + <source>Stack</source> + <translation>Pile</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="191"/> + <source>Environment variables</source> + <translation>Variables d'environnement</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="210"/> + <source>Application pids</source> + <translation>PIDs application</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="240"/> + <source>Start or stop monitoring this process</source> + <translation>Démarrer ou arrêter la surveillance de ce processus</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="256"/> + <source>Close</source> + <translation>Fermer</translation> + </message> +</context> +<context> + <name>RulesDialog</name> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="14"/> + <source>Rule</source> + <translation>Règle</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="118"/> + <source>Node</source> + <translation>Noeud</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="141"/> + <source>Apply rule to all nodes</source> + <translation>Appliquer la règle à tous les noeuds</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="239"/> + <source>From this command line</source> + <translation>Depuis cette ligne de commande</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="206"/> + <source>From this executable</source> + <translation>Depuis cet exécutable</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="751"/> + <source>Action</source> + <translation>Action</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="138"/> + <source>/path/to/executable, .*/bin/executable[0-9\.]+$, ...</source> + <translation type="obsolete">/chemin/vers/executable, .*/bin/executable[0-9\.]+$, ...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="383"/> + <source>To this IP / Network</source> + <translation>vers cette IP / ce réseau</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="792"/> + <source>once</source> + <translation>une seule fois</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="797"/> + <source>30s</source> + <translation>30s</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="802"/> + <source>5m</source> + <translation>5m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="807"/> + <source>15m</source> + <translation>15m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="812"/> + <source>30m</source> + <translation>30m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="817"/> + <source>1h</source> + <translation>1h</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="230"/> + <source>until restart</source> + <translation type="obsolete">hasta reiniciar (el servicio)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="827"/> + <source>always</source> + <translation>toujours</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="293"/> + <source>To this port</source> + <translation>vers cette interface (port)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="169"/> + <source>From this user ID</source> + <translation>depuis cet utilisateur (ID)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="419"/> + <source>Commas or spaces are not allowed to specify multiple domains. + +Use regular expressions instead: +.*(opensnitch|duckduckgo).com +.*\.google.com + +or a single domain: +www.gnu.org - it'll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ... +gnu.org - it'll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source> + <translation>ni virgules ni espaces ne sont autorisés pour spécifier de multiples domaines. + +Utiliser à la place des 'expressions régulières' (RegExp): +.*(opensnitch|duckduckgo).com +.*\.google.com + +ou bien un seul domaine: +www.gnu.org - correspond uniquement à www.gnu.org, mais pas ftp.gnu.org, ou www2.gnu.org, ... +gnu.org - correspond uniquement à gnu.org, mais pas www.gnu.org, ou ftp.gnu.org, ...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="430"/> + <source>www.domain.org, .*\.domain.org</source> + <translation>www.domain.org, .*\.domain.org</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="323"/> + <source><html><head/><body><p>Only TCP, UDP or UDPLITE are allowed</p><p>You can use regexp, i.e.: ^(TCP|UDP)$</p></body></html></source> + <translation><html><head/><body><p>Seuls TCP, UDP ou UDPLITE sont permis</p><p>On peut employer des expressions régulières (regexp), par ex.: ^(TCP|UDP)$</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="333"/> + <source>TCP</source> + <translation>TCP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="338"/> + <source>UDP</source> + <translation>UDP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="343"/> + <source>UDPLITE</source> + <translation>UDPLITE</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="348"/> + <source>TCP6</source> + <translation>TCP6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="353"/> + <source>UDP6</source> + <translation>UDP6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="358"/> + <source>UDPLITE6</source> + <translation>UDPLITE6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="440"/> + <source>You can specify a single IP: +- 192.168.1.1 + +or a regular expression: +- 192\.168\.1\.[0-9]+ + +multiple IPs: +- ^(192\.168\.1\.1|172\.16\.0\.1)$ + +You can also specify a subnet: +- 192.168.1.0/24 + +Note: Commas or spaces are not allowed to separate IPs or networks.</source> + <translation>On peut spécifier une IP unique: +- 192.168.1.1 + +ou une "expression régulière": +- 192\.168\.1\.[0-9]+ +pluseurs IPs: +- ^(192\.168\.1\.1|172\.16\.0\.1)$ + +On peut aussi spécifier un sous-réseau: +- 192.168.1.0/24 + +Note : on ne peut pas utiliser virgules ou espaces pour séparer plusieurs IP ou réseaux.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="459"/> + <source>LAN</source> + <translation>LAN</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="464"/> + <source>127.0.0.0/8</source> + <translation>127.0.0.0/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="469"/> + <source>192.168.0.0/24</source> + <translation>192.168.0.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="474"/> + <source>192.168.1.0/24</source> + <translation>192.168.1.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="479"/> + <source>192.168.2.0/24</source> + <translation>192.168.2.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="484"/> + <source>192.168.0.0/16</source> + <translation>192.168.0.0/16</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="489"/> + <source>169.254.0.0/16</source> + <translation>169.254.0.0/16</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="494"/> + <source>172.16.0.0/12</source> + <translation>172.16.0.0/12</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="499"/> + <source>10.0.0.0/8</source> + <translation>10.0.0.0/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="504"/> + <source>::1/128</source> + <translation>::1/128</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="509"/> + <source>fc00::/7</source> + <translation>fc00::/7</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="514"/> + <source>ff00::/8</source> + <translation>ff00::/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="519"/> + <source>fe80::/10</source> + <translation>fe80::/10</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="524"/> + <source>fd00::/8</source> + <translation>fd00::/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="784"/> + <source>Duration</source> + <translation>Durée</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="376"/> + <source>Protocol</source> + <translation>Protocole</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="300"/> + <source>To this host</source> + <translation>Vers cet hôte</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="843"/> + <source>Deny</source> + <translation>Refuser</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="877"/> + <source>Allow</source> + <translation>Autoriser</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="899"/> + <source>Name</source> + <translation>Nom</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="22"/> + <source>Enable</source> + <translation>Activer</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="706"/> + <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them. + +000-allow-localhost +001-deny-broadcast +...</source> + <translation>Les règles étant appliquées par ordre aplhabétique, on peut leur donner priorité grâce à leur nom. + +000-allow-localhost +001-deny-broadcast +...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="713"/> + <source>leave blank to autocreate</source> + <translation>ne pas remplir va créer automatiquement</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="46"/> + <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one. + +You must name the rule in such manner that it'll be checked first, because they're checked in alphabetical order. For example: + +[x] Priority - 000-priority-rule +[ ] Priority - 001-less-priority-rule</source> + <translation>Cocher pour que cette règle ait priorité sur toutes les autres. Aucune autre règle ne sera appliquée après celle-ci. + +Vous devez nommer la règle de façon qu'elle soit testée en premier, par ordre alphabétique. Par exemple: + +[x] Priority - 000-règle-prioritaire +[ ] Priority - 001-règle-moins-prioritaire</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="54"/> + <source>Priority rule</source> + <translation>Règle prioritaire</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="74"/> + <source><html><head/><body><p>By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.<br/></p><p>If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.</p></body></html></source> + <translation><html><head/><body><p>Par défaut, le champ des règles n'est pas sensible à la casse. Par exemple si un processus tente d'atteindre gOOgle.CoM alors que vous avez une règle interdisant .*google.com, la connexion gOOgle.CoM sera bloquée.<br/></p><p>Si vous cochez ceci, vous devrez spécifier la chaîne exacte (domaine, exécutable, ligne de commande) que vous voulez filtrer.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="77"/> + <source>Case-sensitive</source> + <translation>sensible à la casse</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="369"/> + <source><html><head/><body><p>You can specify multiple ports using regular expressions:</p><p><br/></p><p>- 53, 80 or 443:</p><p>^(53|80|443)$</p><p><br/></p><p>- 53, 443 or 5551, 5552, 5553, etc:</p><p>^(53|443|555[0-9])$</p></body></html></source> + <translation><html><head/><body><p>Vous pouvez spécifier plusieurs ports d'interface avec des "expessions régulières":</p><p><br/></p><p>- 53, 80 ou 443:</p><p>^(53|80|443)$</p><p><br/></p><p>- 53, 443 ou 5551, 5552, 5553, etc:</p><p>^(53|443|555[0-9])$</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="822"/> + <source>until reboot</source> + <translation>jusqu'au redémarrage</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="583"/> + <source>To this list of domains</source> + <translation>Vers cette liste de domaines</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="539"/> + <source><html><head/><body><p>Select a directory with lists of domains to block or allow.</p><p>Put inside that directory files with any extension containing lists of domains.</p><p><br/>The format of each entry of a list is as follow (hosts format):</p><p>127.0.0.1 www.domain.com</p><p>or </p><p>0.0.0.0 www.domain.com</p></body></html></source> + <translation type="obsolete"><html><head/><body><p>Choisir un dossier contenant des listes de domaines à autoriser ou interdire.</p><p>Y mettre des fichiers contenant des listes de domaines, avec n'importe quelle extension.</p><p><br/>Le format de chaque entrée (hist format) est comme suit:</p><p>127.0.0.1 www.domain.com</p><p>ou </p><p>0.0.0.0 www.domain.com</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="163"/> + <source>Applications</source> + <translation type="unfinished">Applications</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="216"/> + <source><html><head/><body><p>This field will only match the executable path. It is not modifiable by the user.<br/></p><p>You can use regular expressions to deny executions from /tmp for example:<br/></p><p>^/tmp/.*$</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="229"/> + <source><html><head/><body><p>This field will contain and match the command line that was executed by the user.<br/></p><p>If the user typed the command, only the command will appear:</p><p>telnet 1.2.3.4<br/></p><p>If the user typed the absolute or relative path to the command, that is what will appear:</p><p>/usr/bin/telnet 1.2.3.4</p><p>../../../usr/bin/telnet 1.2.3.4</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="246"/> + <source>From this PID</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="287"/> + <source>Network</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="536"/> + <source>List of domains/IPs</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="542"/> + <source>To this list of network ranges</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="549"/> + <source>To this list of IPs</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="574"/> + <source><html><head/><body><p>Select a directory with files containing list of IPs to block or allow:</p><p>1.2.3.4.5</p><p>1.2.3.4.6</p><p>.</p><p>etc.</p><p>One IP per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="608"/> + <source><html><head/><body><p>Select a directory with files containing list of network ranges to block or allow:</p><p>1.2.3.0/24</p><p>80.34.56.0/20</p><p>.</p><p>etc.<br/></p><p>One Network Range per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="636"/> + <source><html><head/><body><p>Select a directory with lists of domains to block or allow.</p><p>Put inside that directory files with any extension containing lists of domains.</p><p><br/>The format of each entry of a list is as follow (hosts format):</p><p>127.0.0.1 www.domain.com</p><p>or </p><p>0.0.0.0 www.domain.com</p><p>Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="651"/> + <source>To this list of domains +(regular expressions)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="677"/> + <source><html><head/><body><p>Select a directory with files containing regular expressions of domains to block or allow:</p><p>.*\.example\.com</p><p>You can also use a domain as is: &quot;example.com&quot; , and it'll match whatever.example.com, whatever.example.com.localdomain, etc.</p><p>One domain per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="857"/> + <source>Reject</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>StatsDialog</name> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="34"/> + <source>OpenSnitch Network Statistics</source> + <translation>Statistiques réseau Opensnitch</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="290"/> + <source>Save to CSV.</source> + <translation>Enregistrer au format CSV.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="300"/> + <source>Ctrl+S</source> + <translation>contrôle-S</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="351"/> + <source>Create a new rule</source> + <translation>Créer une nouvelle règle</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="381"/> + <source><html><head/><body><p><span style=" font-size:11pt; font-weight:600;">hostname - 192.168.1.1</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-size:11pt; font-weight:600;">hostname - 192.168.1.1</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="420"/> + <source>Status</source> + <translation>Etat</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1665"/> + <source>-</source> + <translation>-</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="464"/> + <source>Start or Stop interception</source> + <translation>Démarre ou stoppe l'interception</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="509"/> + <source>Events</source> + <translation>Evènements</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="94"/> + <source>Filter</source> + <translation>Filtre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="107"/> + <source>Allow</source> + <translation>Autoriser</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="116"/> + <source>Deny</source> + <translation>Refuser</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="143"/> + <source>Ex.: firefox</source> + <translation>Ex.: firefox</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="199"/> + <source>50</source> + <translation>50</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="204"/> + <source>100</source> + <translation>100</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="209"/> + <source>200</source> + <translation>200</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="214"/> + <source>300</source> + <translation>300</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="748"/> + <source>Nodes</source> + <translation>Noeuds</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="554"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on the Addr column to view details of a node)</span></p></body></html></source> + <translation type="obsolete"><html><head/><body><p><span style=" font-size:7pt;">(double cliquer sur la colonne Addr pour voir les détails d'un noeud)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1569"/> + <source>Rules</source> + <translation>Règles</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="857"/> + <source>enable</source> + <translation>activer</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="684"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on the Name column to view details of a rule)</span></p></body></html></source> + <translation type="obsolete">(doble click en la columna Nombre para ver los detalles)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="692"/> + <source>search rule name</source> + <translation type="obsolete">rechercher par nom de règle</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="704"/> + <source>Application rules</source> + <translation>Règle d'applications</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="806"/> + <source>Permanent</source> + <translation>Permanent</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="815"/> + <source>Temporary</source> + <translation>Temporaire</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="933"/> + <source>Hosts</source> + <translation>Hôtes</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1364"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click to view details of an item)</span></p></body></html></source> + <translation type="obsolete"><html><head/><body><p><span style=" font-size:7pt;">(double clic pour voir les détails d'un élément)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1020"/> + <source>Applications</source> + <translation>Applications</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1127"/> + <source>Addresses</source> + <translation>Adresses</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1214"/> + <source>Ports</source> + <translation>Ports (interfaces)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1298"/> + <source>Users</source> + <translation>Utilisateurs</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1404"/> + <source>Connections</source> + <translation>Connexions</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1459"/> + <source>Dropped</source> + <translation>Abandonnée</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1514"/> + <source>Uptime</source> + <translation>durée active (uptime)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1639"/> + <source>Version</source> + <translation>Version</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="227"/> + <source>Delete all intercepted events</source> + <translation>Effacer tous les évènments interceptés</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="864"/> + <source>Edit rule</source> + <translation>Editer règle</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="878"/> + <source>Delete rule</source> + <translation>Effacer règle</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="926"/> + <source>Delete all intercepted hosts</source> + <translation type="obsolete">Effacer tous les hôtes interceptés</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1051"/> + <source>Delete all intercepted applications</source> + <translation type="obsolete">Effacer toutes les applications interceptées</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1159"/> + <source>Delete all intercepted addresses</source> + <translation type="obsolete">Effacer toutes les adresses interceptées</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1261"/> + <source>Delete all intercepted ports</source> + <translation type="obsolete">Effacer tous les ports interceptés</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1371"/> + <source>Delete all intercepted users</source> + <translation type="obsolete">Effacer tous les utilisateurs interceptés</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="699"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on a row to view details of a rule)</span></p></body></html></source> + <translation type="obsolete"><html><head/><body><p><span style=" font-size:7pt;">(double clic sur une ligne pour voir les détails d'une règle)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="912"/> + <source>Delete connections that matched this rule</source> + <translation>Effacer les connexions qui déclenchaient cette règle</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="797"/> + <source>All applications</source> + <translation>Toutes les applications</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="125"/> + <source>Reject</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="177"/> + <source>0</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>contextual_menu</name> + <message> + <location filename="../../../opensnitch/service.py" line="43"/> + <source>Statistics</source> + <translation>Statistiques</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="46"/> + <source>Help</source> + <translation>Aide</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="47"/> + <source>Close</source> + <translation>Fermer</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="44"/> + <source>Enable</source> + <translation>Activer</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="45"/> + <source>Disable</source> + <translation>Désactiver</translation> + </message> +</context> +<context> + <name>menu_close</name> + <message> + <location filename="../../../opensnitch/service.py" line="131"/> + <source>Close</source> + <translation type="obsolete">Cerrar</translation> + </message> +</context> +<context> + <name>menu_help</name> + <message> + <location filename="../../../opensnitch/service.py" line="126"/> + <source>Help</source> + <translation type="obsolete">Ayuda</translation> + </message> +</context> +<context> + <name>menu_statistics</name> + <message> + <location filename="../../../opensnitch/service.py" line="120"/> + <source>Statistics</source> + <translation type="obsolete">Eventos</translation> + </message> +</context> +<context> + <name>notifications</name> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="518"/> + <source>System notifications are not available, you need to install python3-notify2.</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>popups</name> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="91"/> + <source>Allow</source> + <translation>Autoriser</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="92"/> + <source>Deny</source> + <translation>Refuser</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="54"/> + <source>forever</source> + <translation>indéfiniment</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="285"/> + <source>Outgoing connection</source> + <translation>Connexion sortante</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="290"/> + <source>Process launched from:</source> + <translation>Processus lancé par :</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="325"/> + <source>from this command line</source> + <translation>depuis cette ligne de commande</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="321"/> + <source>from this executable</source> + <translation>depuis cet exécutable</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="208"/> + <source>Unknown process</source> + <translation type="obsolete">Proceso no encontrado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="52"/> + <source>until reboot</source> + <translation>jusqu'au redémarrage</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="327"/> + <source>to port {0}</source> + <translation>vers le port {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="222"/> + <source><b>%s</b> is connecting to <b>%s</b> on %s port %d</source> + <translation type="obsolete"><b>%s</b> está conectándose a <b>%s</b> en el puerto %s %d</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="228"/> + <source><b>Remote</b> process <b>%s</b> running on <b>%s</b> is connecting to <b>%s</b> on %s port %d</source> + <translation type="obsolete">El proceso <b>remoto %s</b> ejecutándose en <b>%s</b> está conectándose a <b>%s</b> en el puerto %s %d</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="390"/> + <source>to {0}</source> + <translation>vers {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="330"/> + <source>from user {0}</source> + <translation>de l'utilisateur {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="347"/> + <source>to {0}.*</source> + <translation>vers {0}.*</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="400"/> + <source>to *.{0}</source> + <translation>vers *.{0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="378"/> + <source>to *{0}</source> + <translation type="obsolete">vers *{0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="434"/> + <source><b>Remote</b> process %s running on <b>%s</b></source> + <translation>processus <b>Distant</b> %s tournant sur <b>%s</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="438"/> + <source>is connecting to <b>%s</b> on %s port %d</source> + <translation>se connecte à <b>%s</b> sur %s port %d</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="444"/> + <source>is attempting to resolve <b>%s</b> via %s, %s port %d</source> + <translation>tente de résoudre (connecter) <b>%s</b> via %s, %s port %d</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="334"/> + <source>from this PID</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/notifications.py" line="105"/> + <source>New outgoing connection</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>popups2</name> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="254"/> + <source><b>Remote</b> process <b>%s</b> running on <b>%s</b> is connecting to <b>%s</b> on %s port %d</source> + <translation type="obsolete">El proceso <b>remoto %s</b> ejecutándose en <b>%s</b> está conectándose a <b>%s</b> en el puerto %s %d</translation> + </message> +</context> +<context> + <name>preferences</name> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="171"/> + <source>Exception saving config: %s</source> + <translation type="obsolete">Error al guarda la configuración: %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="177"/> + <source>Applying configuration on %s ...</source> + <translation type="obsolete">Aplicando configuración en %s ...</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="208"/> + <source>Server address can not be empty</source> + <translation>L'adresse du serveur ne peut être vide</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="227"/> + <source>Error loading %s configuration</source> + <translation type="obsolete">Error al cargar la configuración %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="448"/> + <source>Configuration applied.</source> + <translation>Configuration appliquée.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="257"/> + <source>Error applying configuration: %s</source> + <translation type="obsolete">Error al aplicar la configuración: %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="299"/> + <source>Exception saving config: {0}</source> + <translation>Config enregistrant les exceptions : {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="389"/> + <source>Applying configuration on {0} ...</source> + <translation>Applique la configuration à {0} ...</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="238"/> + <source>Error loading {0} configuration</source> + <translation>Erreur au chargement configuration {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="450"/> + <source>Error applying configuration: {0}</source> + <translation>Erreur en tentant d'appliquer la configution : {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="323"/> + <source>Warning</source> + <translation>Alerte</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="323"/> + <source>You must select a file for the database<br>or choose "In memory" type.</source> + <translation>Vous devez sélectionner un fichier pour la base de données<br>ou choisir le type "en mémoire".</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="329"/> + <source>DB type changed</source> + <translation>type de base de données changé</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="329"/> + <source>Restart the GUI in order effects to take effect</source> + <translation>Prendra effet au redémarrage de l'interface graphique</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="480"/> + <source>Hover the mouse over the texts to display the help<br><br>Don't forget to visit the wiki: <a href="{0}">{0}</a></source> + <translation>Glisser la souris sur les textes pour afficher l'aide<br><br>N'hésitez pas à visiter le Wiki : <a href="{0}">{0}</a></translation> + </message> +</context> +<context> + <name>proc_details</name> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="97"/> + <source><b>Error loading process information:</b> <br><br> + +</source> + <translation><b>Erreur au chargement d'information sur le processus:</b> <br><br> + +</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="116"/> + <source><b>Error stopping monitoring process:</b><br><br></source> + <translation><b>Erreur en stoppant le processus de monitoring:</b><br><br></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="156"/> + <source>loading...</source> + <translation>chargement...</translation> + </message> +</context> +<context> + <name>rules</name> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="162"/> + <source>There're no nodes connected.</source> + <translation>Aucun noeud n'est connecté.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="203"/> + <source>Rule applied.</source> + <translation>Règle appliquée.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="123"/> + <source>Error applying rule: %s</source> + <translation type="obsolete">Error al aplicar la regla: %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="527"/> + <source>protocol can not be empty, or uncheck it</source> + <translation>le protocole ne peut être vide, ou bien le désélectionner</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="541"/> + <source>Protocol regexp error</source> + <translation>Erreur à l'interprétation du RegExp protocole</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="545"/> + <source>process path can not be empty</source> + <translation>le chemin vers le processus ne peut être vide</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="559"/> + <source>Process path regexp error</source> + <translation>erreur RegExp dans le chemin vers le processus</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="563"/> + <source>command line can not be empty</source> + <translation>la ligne de commande ne peut être vide</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="577"/> + <source>Command line regexp error</source> + <translation>Erreur RegExp dans la ligne de commande</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="581"/> + <source>Dest port can not be empty</source> + <translation>l'interface (port) destination ne peut être vide</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="595"/> + <source>Dst port regexp error</source> + <translation>ereur RegExp sur l'interface (port) de destination</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="599"/> + <source>Dest host can not be empty</source> + <translation>l'hôte de destination ne peut être vide</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="613"/> + <source>Dst host regexp error</source> + <translation>erreur RegExp sur l'hôte de destination</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="617"/> + <source>Dest IP/Network can not be empty</source> + <translation>l'IP / réseau de destination ne peut être vide</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="639"/> + <source>Dst IP regexp error</source> + <translation>erreur RegExp sur l'IP destination</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="651"/> + <source>User ID can not be empty</source> + <translation>l'ID utilisateur ne peut être vide</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="665"/> + <source>User ID regexp error</source> + <translation>erreur RegExp sur l'ID utilisateur</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="205"/> + <source>Error applying rule: {0}</source> + <translation>Erreur en appliquant la règle : {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="739"/> + <source>Lists field cannot be empty</source> + <translation>le champ "listes" ne peut être vide</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="741"/> + <source>Lists field must be a directory</source> + <translation>le champ Listes doit être un répertoire</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="777"/> + <source><b>Rule not supported</b></source> + <translation><b>RRègle non supportée</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="429"/> + <source><b>Error loading rule</b></source> + <translation><b>Erreur au chargement de la règle</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="179"/> + <source>There's already a rule with this name.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="669"/> + <source>PID field can not be empty</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="683"/> + <source>PID field regexp error</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="764"/> + <source>Select at least one field.</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>stats</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="310"/> + <source>Not running</source> + <translation>Inactif</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="311"/> + <source>Disabled</source> + <translation>désactivé</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="312"/> + <source>Running</source> + <translation>Actif</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="412"/> + <source>OpenSnitch Network Statistics</source> + <translation type="obsolete">Eventos de OpenSnitch</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="414"/> + <source>OpenSnitch Network Statistics for</source> + <translation type="obsolete">Eventos de OpenSnitch de</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="761"/> + <source> Your are about to delete this rule. </source> + <translation> Vous êtes sur le point d'effacer cette règle. </translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1244"/> + <source> Are you sure?</source> + <translation> Êtes-vous sûr?</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="568"/> + <source>OpenSnitch Network Statistics {0}</source> + <translation>Statistiques réseau OpenSnitch {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="570"/> + <source>OpenSnitch Network Statistics for {0}</source> + <translation>Statistique réseau OpenSnitch pour {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <translation type="obsolete">Nombre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="294"/> + <source>Address</source> + <translation type="obsolete">Dirección</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="176"/> + <source>Status</source> + <translation type="obsolete">Estado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="177"/> + <source>Hostname</source> + <translation type="obsolete">Hostname</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="183"/> + <source>Version</source> + <translation type="obsolete">Versión</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="180"/> + <source>Rules</source> + <translation type="obsolete">Reglas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Time</source> + <translation type="obsolete">Hora</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Action</source> + <translation type="obsolete">Acción</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Duration</source> + <translation type="obsolete">Duración</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Node</source> + <translation type="obsolete">Nodo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="253"/> + <source>Hits</source> + <translation type="unfinished">Total</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>Protocol</source> + <translation type="obsolete">Protocolo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1837"/> + <source>Save as CSV</source> + <translation>Enregistrer en CSV</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>Enabled</source> + <translation type="obsolete">Habilitado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="738"/> + <source>Delete</source> + <translation>Effacer</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="948"/> + <source>always</source> + <translation type="obsolete">siempre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="580"/> + <source><b>Error:</b><br><br>{0}</source> + <translation type="obsolete"><b>Error:</b><br><br>{0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="731"/> + <source>Disable</source> + <translation>Désactiver</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="733"/> + <source>Enable</source> + <translation>Activer</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="736"/> + <source>Duplicate</source> + <translation>Dupliquer</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="737"/> + <source>Edit</source> + <translation>Editer</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="891"/> + <source>Rule not found by that name and node</source> + <translation>Pas trouvé de règle pour ces nom et noeud</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="921"/> + <source><b>Error:</b><br><br></source> + <comment>{0}</comment> + <translation><b>Erreur:</b><br><br></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="928"/> + <source>Warning:</source> + <translation>Alerte :</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="717"/> + <source>Allow</source> + <translation>Autoriser</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="718"/> + <source>Deny</source> + <translation>Refuser</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="722"/> + <source>Always</source> + <translation>Toujours</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="723"/> + <source>Until reboot</source> + <translation>Jusqu'au redémarrage</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1244"/> + <source> You are about to delete this rule. </source> + <translation> Vous êtes sur le point d'effacer cette règle. </translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="306"/> + <source>Process</source> + <translation type="obsolete">Aplicación</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="307"/> + <source>Destination</source> + <translation type="obsolete">Destino</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="308"/> + <source>Rule</source> + <translation type="obsolete">Regla</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="309"/> + <source>UserID</source> + <translation type="obsolete">UserID</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="174"/> + <source>LastConnection</source> + <translation type="obsolete">Última Conexión</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <comment>xxxxx</comment> + <translation type="obsolete">Nombre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Nombre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="294"/> + <source>Address</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Dirección</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="295"/> + <source>Status</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Estado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="296"/> + <source>Hostname</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Hostname</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="297"/> + <source>Version</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Versión</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="298"/> + <source>Rules</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Reglas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Time</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Hora</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Action</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Acción</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Duration</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Duración</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Node</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Nodo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>Enabled</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Habilitado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="304"/> + <source>Hits</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Total</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>Protocol</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Protocolo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="306"/> + <source>Process</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Aplicación</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="307"/> + <source>Destination</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Destino</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="308"/> + <source>Rule</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Regla</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="309"/> + <source>UserID</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">UserID</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="310"/> + <source>LastConnection</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">ÚltimaConexión</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="392"/> + <source>Name</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Nom</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="288"/> + <source>Address</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Adresse</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="379"/> + <source>Status</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Etat</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="380"/> + <source>Hostname</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Nom d'hôte</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="386"/> + <source>Version</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Version</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="383"/> + <source>Rules</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Règles</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="390"/> + <source>Time</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Temps</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="395"/> + <source>Action</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Action</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="396"/> + <source>Duration</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Durée</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="391"/> + <source>Node</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Noeud</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="393"/> + <source>Enabled</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Activé</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="405"/> + <source>Hits</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Connexions</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Protocol</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Protocole</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Process</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Processus</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Destination</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Destination</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="306"/> + <source>Rule</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Règle</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="307"/> + <source>UserID</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>ID utilisateur</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="377"/> + <source>LastConnection</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>DernièreConnexion</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Args</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Args</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>DstIP</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>DstIP</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="304"/> + <source>DstHost</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>DstHost</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>DstPort</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>DstPort</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="179"/> + <source>Uptime</source> + <translation type="obsolete">durée active (uptime)</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="181"/> + <source>Connections</source> + <translation type="obsolete">Connexions</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="182"/> + <source>Dropped</source> + <translation type="obsolete">Abandonnée</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="252"/> + <source>What</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="709"/> + <source>Apply to</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="719"/> + <source>Reject</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="25"/> + <source>Network name</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="378"/> + <source>Addr</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="382"/> + <source>Uptime</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">durée active (uptime)</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="384"/> + <source>Connections</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">Connexions</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="385"/> + <source>Dropped</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">Abandonnée</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="404"/> + <source>What</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="394"/> + <source>Precedence</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="644"/> + <source>New node connected</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>stats_deleterule</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="774"/> + <source> Your are about to delete this rule. </source> + <translation type="obsolete"> Estás a punto de borrar esta regla. </translation> + </message> +</context> +<context> + <name>stats_deleterule2</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="776"/> + <source> Are you sure?</source> + <translation type="obsolete"> ¿Estás seguro?</translation> + </message> +</context> +<context> + <name>stats_disabled</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="74"/> + <source>Disabled</source> + <translation type="obsolete">Deshabilitado</translation> + </message> +</context> +<context> + <name>stats_notrunning</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="73"/> + <source>Not running</source> + <translation type="obsolete">Parado</translation> + </message> +</context> +<context> + <name>stats_running</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="75"/> + <source>Running</source> + <translation type="obsolete">Interceptando</translation> + </message> +</context> +<context> + <name>stats_wintitle</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="409"/> + <source>OpenSnitch Network Statistics</source> + <translation type="obsolete">Eventos de red OpenSnitch</translation> + </message> +</context> +<context> + <name>stats_wintitle2</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="411"/> + <source>OpenSnitch Network Statistics for</source> + <translation type="obsolete">Eventos de OpenSnitch de</translation> + </message> +</context> +</TS> diff --git a/ui/i18n/locales/hu_HU/opensnitch-hu_HU.ts b/ui/i18n/locales/hu_HU/opensnitch-hu_HU.ts new file mode 100644 index 0000000..8331722 --- /dev/null +++ b/ui/i18n/locales/hu_HU/opensnitch-hu_HU.ts @@ -0,0 +1,2256 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.0" language="hu_HU" sourcelanguage=""> +<context> + <name>Dialog</name> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="34"/> + <source>opensnitch-qt</source> + <translation>opensnitch-qt</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="150"/> + <source>Chromium Web Browser</source> + <translation type="obsolete">Chromium webböngésző</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="179"/> + <source><html><head/><body><p>/opt/google/chrome/bin/chrome --something abc --more-long def --for-word-wrapping</p></body></html></source> + <translation type="obsolete"><html><head/><body><p>/opt/google/chrome/bin/chrome --valami ábécé --hosszabb-ideig def --szócsomagoláshoz</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="226"/> + <source>(/path/to/bin/chromium)</source> + <translation type="obsolete">(/út/a/bináris/Chromiumhoz)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="679"/> + <source>from this executable</source> + <translation>ettől a futtatható fájltól</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="684"/> + <source>from this command line</source> + <translation>erről a parancssorról</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="689"/> + <source>this destination port</source> + <translation>ezt a célkikötőt</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="694"/> + <source>this user</source> + <translation>ezt a felhasználót</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="699"/> + <source>this destination ip</source> + <translation>ezt a cél IP címet</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="847"/> + <source>+</source> + <translation>+</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="728"/> + <source>once</source> + <translation>egyszer</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="733"/> + <source>30s</source> + <translation>30 másodperc</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="738"/> + <source>5m</source> + <translation>5 perc</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="743"/> + <source>15m</source> + <translation>15 perc</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="748"/> + <source>30m</source> + <translation type="unfinished">30 perc</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="753"/> + <source>1h</source> + <translation>1 óra</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="758"/> + <source>until reboot</source> + <translation>újraindításig</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="763"/> + <source>forever</source> + <translation>örökre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="789"/> + <source>Deny</source> + <translation>Megtagadás</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="818"/> + <source>Allow</source> + <translation>Engedélyezés</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="299"/> + <source>User ID</source> + <translation>Felhasználói azonosító</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="333"/> + <source><html><head/><body><p><span style=" font-weight:600;">Executed from</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-weight:600;">Futtatható fájl innen:</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="630"/> + <source>TextLabel</source> + <translation>Szövegcímke</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="426"/> + <source>Source IP</source> + <translation>Forrás IP-cím</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="449"/> + <source>Process ID</source> + <translation>Folyamatazonosító</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="582"/> + <source>Destination IP</source> + <translation>Cél IP-címe</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="605"/> + <source>Dst Port</source> + <translation>Célkikötő</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="271"/> + <source>Chromium Web Browser wants to connect to www.evilsocket.net on tcp port 443. And maybe to www.goodsocket.net on port 344</source> + <translation type="obsolete">A Chromium webböngésző csatlakozni akar a www.evilsocket.net webhelyhez a 443-as TCP-kikötőn. És talán a www.goodsocket.net webhelyhez a 344-es TCP-kikötőn</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="704"/> + <source>from this PID</source> + <translation>ebből a folyamatazonosítóból</translation> + </message> +</context> +<context> + <name>New node connected</name> +</context> +<context> + <name>PreferencesDialog</name> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="14"/> + <source>Preferences</source> + <translation>Beállítások</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="472"/> + <source>UI</source> + <translation>Felhasználói felület</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="83"/> + <source>Pop-ups default options</source> + <translation type="obsolete">Az előugró ablakok alapértelmezett beállításai</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="367"/> + <source>Pop-ups default position on screen</source> + <translation type="obsolete">Felugró ablakok alapértelmezett helye a képernyőn</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="350"/> + <source>Show advanced view by default</source> + <translation>Alapértelmezés szerint a haladó nézet megjelenítése</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="863"/> + <source>once</source> + <translation>egyszer</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="210"/> + <source>30s</source> + <translation>30 másodperc</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="215"/> + <source>5m</source> + <translation>5 perc</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="220"/> + <source>15m</source> + <translation>15 perc</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="225"/> + <source>30m</source> + <translation>30 perc</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="230"/> + <source>1h</source> + <translation>1 óra</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="235"/> + <source>until reboot</source> + <translation>újraindításig</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="240"/> + <source>forever</source> + <translation>örökre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="162"/> + <source>Pop-up default action</source> + <translation type="obsolete">Felugró ablak alapértelmezett művelete</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="665"/> + <source>Action</source> + <translation>Művelet</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="314"/> + <source>Default target</source> + <translation>Alapértelmezett cél</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="366"/> + <source><html><head/><body><p>If checked, the pop-ups will be displayed with the advanced view active.</p></body></html></source> + <translation><html><head/><body><p>Ha be van jelölve, az előugró ablakok aktív haladó nézettel jelennek meg.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="905"/> + <source>deny</source> + <translation>megtagadás</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="914"/> + <source>allow</source> + <translation>engedélyezés</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="274"/> + <source>by executable</source> + <translation>futtatható fájl szerint</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="279"/> + <source>by command line</source> + <translation>parancssor szerint</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="284"/> + <source>by destination port</source> + <translation>rendeltetési kikötő szerint</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="289"/> + <source>by destination ip</source> + <translation>rendeltetési hely IP címe szerint</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="294"/> + <source>by user id</source> + <translation>felhasználói azonosító szerint</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="170"/> + <source>center</source> + <translation>középre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="175"/> + <source>top right</source> + <translation>jobb felső</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="180"/> + <source>bottom right</source> + <translation>jobb alsó</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="185"/> + <source>top left</source> + <translation>bal felső</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="190"/> + <source>bottom left</source> + <translation>bal alsó</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="331"/> + <source>Pop-up default duration</source> + <translation>Előugró ablak alapértelmezett időtartama</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="334"/> + <source>Duration</source> + <translation>Időtartam</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="254"/> + <source><html><head/><body><p>By default when a new pop-up appears, in its simplest form, you'll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).</p><p>With these options, you can choose multiple fields to filter connections for.</p></body></html></source> + <translation><html><head/><body><p>Alapértelmezés szerint, amikor egy új előugró ablak jelenik meg, a legegyszerűbb formájában képes lesz a kapcsolatok vagy alkalmazások szűrésére a kapcsolat egy tulajdonságával (futtatható fájl, kikötő, IP stb.).</p><p>Ezekkel az opciókkal több mezőt is választhat, amelyekhez a kapcsolatokat szűrni kívánja.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="257"/> + <source>Filter connections also by:</source> + <translation>Szűrje a csatlakozásokat az alábbiak szerint is:</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="81"/> + <source>User ID</source> + <translation>Felhasználói azonosító</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="97"/> + <source>Destination port</source> + <translation>Célkikötő</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="113"/> + <source>Destination IP</source> + <translation>Cél IP-címe</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="406"/> + <source>Disable pop-ups, only display an alert</source> + <translation type="obsolete">Tiltsa le a előugró elemet, csak riasztást jelenítsen meg</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="451"/> + <source><html><head/><body><p>This timeout is the countdown you see when a pop-up dialog is shown.</p><p>If the pop-up is not answered, the default options will be applied.</p></body></html></source> + <translation><html><head/><body><p>Ez az időkorlát az a visszaszámlálás, amelyet akkor láthat, amikor egy felbukkanó párbeszédpanel jelenik meg.</p><p>Ha nem válaszol a felbukkanó párbeszédpanelre, akkor az alapértelmezett beállítások kerülnek alkalmazásra.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="454"/> + <source>Default timeout</source> + <translation>Alapértelmezett időtúllépés</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="711"/> + <source>Nodes</source> + <translation>Csomópontok</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="717"/> + <source>Process monitor method</source> + <translation>Folyamatfigyelés módszer</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="734"/> + <source><html><head/><body><p>Log file to write logs.<br/></p><p>/dev/stdout will print logs to the standard output.</p></body></html></source> + <translation><html><head/><body><p>Naplófájl a naplók írásához.<br/></p><p>A /dev/stdout naplókat nyomtat a normál kimenetre.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="737"/> + <source>Log file</source> + <translation>Naplófájl</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="751"/> + <source><html><head/><body><p>The default duration will take place when there's no UI connected.</p></body></html></source> + <translation><html><head/><body><p>Az alapértelmezett időtartam akkor kerül végrehajtásra, ha nincs csatlakoztatva felhasználói felület.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="754"/> + <source>Default duration</source> + <translation>Alapértelmezett időtartam</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="767"/> + <source>Apply configuration to all nodes</source> + <translation>Beállítások alkalmazása az összes csomópontra</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="790"/> + <source><html><head/><body><p>The default action will take place when there's no UI connected.</p></body></html></source> + <translation><html><head/><body><p>Az alapértelmezett művelet akkor kerül végrehajtásra, ha nincs csatlakoztatva felhasználói felület.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="483"/> + <source>Default action</source> + <translation type="obsolete">Alapértelmezett művelet</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="809"/> + <source>HostName</source> + <translation>Állomásnév</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="671"/> + <source>proc</source> + <translation type="obsolete">proc</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="676"/> + <source>ebpf</source> + <translation type="obsolete">ebpf</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="681"/> + <source>audit</source> + <translation type="obsolete">audit</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="686"/> + <source>ftrace</source> + <translation type="obsolete">ftrace</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="868"/> + <source>until restart</source> + <translation>újraindításáig</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="873"/> + <source>always</source> + <translation>mindig</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="881"/> + <source><html><head/><body><p>Address of the node.</p><p>Default: unix:///tmp/osui.sock (unix:// is mandatory if it's a Unix socket)</p><p>It can also be an IP address with the port: 127.0.0.1:50051</p></body></html></source> + <translation><html><head/><body><p>A csomópont címe.</p><p>Alapértelmezett: unix:///tmp/osui.sock (Az „unix://” kötelező, ha Unix szoftvercsatorna)</p><p>Lehet IP-cím is a kikötővel: 127.0.0.1:50051</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="884"/> + <source>Address</source> + <translation>Cím</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="578"/> + <source><html><head/><body><p>If checked, opensnitch will prompt you to allow or deny connections that don't have an asocciated PID, due to several reasons.</p><p>The pop-up dialog will only contain information about the network connection.</p></body></html></source> + <translation type="obsolete"><html><head/><body><p>Ha be van jelölve, az OpenSnitch több okból is meg fogja engedni vagy megtagadja azokat a kapcsolatokat, amelyek nem rendelkeznek társított folyamatazonosítóval.</p><p>A felbukkanó párbeszédpanel csak a hálózati kapcsolatról tartalmaz információkat.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="581"/> + <source>Intercept Unknown Connections</source> + <translation type="obsolete">Ismeretlen kapcsolatok elfogása</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="932"/> + <source>Version</source> + <translation>Változat</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="778"/> + <source>DEBUG</source> + <translation type="obsolete">HIBAKERESÉS</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="783"/> + <source>INFO</source> + <translation type="obsolete">TÁJÉKOTTATÁS</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="788"/> + <source>IMPORTANT</source> + <translation type="obsolete">FONTOS</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="793"/> + <source>WARNING</source> + <translation type="obsolete">FIGYELMEZTETÉS</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="798"/> + <source>ERROR</source> + <translation type="obsolete">HIBA</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="803"/> + <source>FATAL</source> + <translation type="obsolete">VÉGZETES</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="983"/> + <source>unix:///tmp/osui.sock</source> + <translation>unix:///tmp/osui.sock</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="995"/> + <source>/var/log/opensnitchd.log</source> + <translation>/var/log/opensnitchd.log</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1000"/> + <source>/dev/stdout</source> + <translation>/dev/stdout</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1024"/> + <source>Default log level</source> + <translation>Alapértelmezett naplószint</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1039"/> + <source>Database</source> + <translation>Adatbázis</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1093"/> + <source>Database type</source> + <translation>Adatbázistípus</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1100"/> + <source>Select</source> + <translation>Kijelölés</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1074"/> + <source>In memory</source> + <translation>Memóriabeli</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1079"/> + <source>File</source> + <translation>Fájl</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1345"/> + <source>Close</source> + <translation>Bezárás</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1356"/> + <source>Apply</source> + <translation>Alkalmazás</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1367"/> + <source>Save</source> + <translation>Mentés</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="347"/> + <source>The advanced view allows you to easily select multiple fields to filter connections</source> + <translation>A haladó nézet lehetővé teszi több mező egyszerű kiválasztását a kapcsolatok szűréséhez</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="110"/> + <source>If checked, this field will be selected when a pop-up is displayed</source> + <translation>Ha be van jelölve, akkor ez a mező lesz kiválasztva, amikor egy előugró jelenik meg</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="150"/> + <source><html><head/><body><p>Pop-up default action.</p><p>When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.</p><p><br/></p><p>While a pop-up is asking the user to allow or deny a connection:</p><p>1. new outgoing connections are denied.</p><p>2. known connections are allowed or denied based on the rules defined by the user.</p></body></html></source> + <translation><html><head/><body><p>Előugró ablak alapértelmezett művelete.</p><p>Amikor új kimenő kapcsolat jön létre, ez a művelet alapértelmezés szerint ki lesz választva, tehát ha az időtúllépés aktiválódik, akkor ez az opció lesz alkalmazva.</p><p><br/></p><p>Miközben egy előugró ablak kéri a felhasználót, hogy engedélyezze vagy tagadja meg a kapcsolatot:</p><p>1. megtagadják az új kimenő kapcsolatokat.</p><p>2. az ismert kapcsolatok a felhasználó által meghatározott szabályok alapján engedélyezhetők vagy megtagadhatók.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="793"/> + <source>Default action when the GUI is disconnected</source> + <translation>Alapértelmezett művelet a grafikus felhasználói felület leválasztása esetén</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="894"/> + <source>Debug invalid connections</source> + <translation>Érvénytelen kapcsolatok hibakeresése</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="39"/> + <source>Pop-ups</source> + <translation>Előugró ablakok</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="64"/> + <source>Default options</source> + <translation>Alapértelmezett beállítások</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="321"/> + <source>Default position on screen</source> + <translation>Alapértelmezett hely a képernyőn</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="479"/> + <source>any temporary rules</source> + <translation>bármilyen ideiglenes szabályt</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="492"/> + <source><html><head/><body><p>When this option is selected, the rules of the selected duration won't be added to the list of temporary rules in the GUI.</p><p><br/></p><p>Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</p></body></html></source> + <translation><html><head/><body><p>Ha ezt az opciót választja, a kiválasztott időtartam szabályai nem kerülnek hozzáadásra a grafikus felhasználói felület ideiglenes szabályainak listájához.</p><p><br/></p><p>Az ideiglenes szabályok továbbra is érvényesek, és használhatja őket, amikor a rendszer kéri az új kapcsolat engedélyezését/elutasítását.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="495"/> + <source>Don't save rules of duration</source> + <translation>Ne mentse az időtartam szabályait</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="601"/> + <source>Time</source> + <translation>Idő</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="681"/> + <source>Destination</source> + <translation>Cél</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="649"/> + <source>Protocol</source> + <translation>Protokoll</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="697"/> + <source>Process</source> + <translation>Folyamat</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="617"/> + <source>Rule</source> + <translation>Szabály</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="633"/> + <source>Node</source> + <translation>Csomópont</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="723"/> + <source><html><head/><body><p>If checked, opensnitch will prompt you to allow or deny connections that don't have an asocciated PID, due to several reasons, mostly due to bad state connections.</p><p>The pop-up dialog will only contain information about the network connection.</p><p>There're some scenarios where these are valid connections though, like when establishing a VPN using wireguard.</p></body></html></source> + <translation type="obsolete"><html><head/><body><p>Ha be van jelölve, az OpenSnitch felszólítja Önt, hogy engedélyezze vagy utasítsa el azokat a kapcsolatokat, amelyek nem rendelkeznek aszocizált folyamatazonosítóval, több okból, főleg a rossz állapotú kapcsolatok miatt.</p><p>Az előugró párbeszédablak csak a hálózati kapcsolatra vonatkozó adatokat tartalmazza.</p><p>Vannak azonban olyan esetek, amikor ezek érvényes kapcsolatok, például amikor virtuális magánhálózatot hoznak létre a WireGuard használatával.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="589"/> + <source>Events tab columns</source> + <translation>Események lap oszlopai</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="299"/> + <source>by PID</source> + <translation>folyamatazonosító által</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="461"/> + <source>Disable pop-ups, only display an notification</source> + <translation>Előugró ablakok letiltása, csak értesítések megjelenítése</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="508"/> + <source>Desktop notifications</source> + <translation>Asztali értesítések</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="526"/> + <source>Use system notifications</source> + <translation>Rendszerértesítések használata</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="542"/> + <source>Use Qt notifications</source> + <translation>Qt-értesítések használata</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="571"/> + <source>Test</source> + <translation>Tesztelés</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="891"/> + <source><html><head/><body><p>If checked, OpenSnitch will prompt you to allow or deny connections that don't have an associated PID, due to several reasons, mostly due to bad state connections.</p><p>The pop-up dialog will only contain information about the network connection.</p><p>There're some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.</p></body></html></source> + <translation><html><head/><body><p>Ha be van jelölve, az OpenSnitch felkéri, hogy engedélyezze vagy tiltsa le azokat a kapcsolatokat, amelyekhez nem tartozik folyamatazonosító, több okból is, főleg a rossz állapotú kapcsolatok miatt.</p><p>A felugró párbeszédpanel csak a hálózati kapcsolatra vonatkozó információkat tartalmaz.</p><p>Vannak olyan esetek, amikor ezek érvényes kapcsolatok, például virtuális magánhálózat WireGuard segítségével történő létesítésekor.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1178"/> + <source>minutes</source> + <translation>perc</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1204"/> + <source>Minutes between events purges</source> + <translation>Eseménytisztítások közötti percek száma</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1227"/> + <source>days</source> + <translation>nap</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1237"/> + <source>Maximum days of events to keep</source> + <translation>Események megtartására nyitva álló napok száma</translation> + </message> +</context> +<context> + <name>ProcessDetailsDialog</name> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="14"/> + <source>Process details</source> + <translation>Folyamat részletei</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="61"/> + <source>loading...</source> + <translation>betöltés…</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="81"/> + <source>CWD: loading...</source> + <translation>CWD: betöltés…</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="93"/> + <source>mem stats: loading...</source> + <translation>memória statisztika: betöltés…</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="121"/> + <source>Status</source> + <translation>Állapot</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="135"/> + <source>Open files</source> + <translation>Fájlok megnyitása</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="149"/> + <source>I/O Statistics</source> + <translation>I/O statisztika</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="163"/> + <source>Memory mapped files</source> + <translation>Memóriába ágyazott fájlok</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="177"/> + <source>Stack</source> + <translation>Verem</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="191"/> + <source>Environment variables</source> + <translation>Környezeti változók</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="210"/> + <source>Application pids</source> + <translation>Alkalmazás folyamatazonosítók</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="240"/> + <source>Start or stop monitoring this process</source> + <translation>Folyamatfigyelés elindítsa vagy leállítása</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="256"/> + <source>Close</source> + <translation>Bezárás</translation> + </message> +</context> +<context> + <name>RulesDialog</name> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="14"/> + <source>Rule</source> + <translation>Szabály</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="118"/> + <source>Node</source> + <translation>Csomópont</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="141"/> + <source>Apply rule to all nodes</source> + <translation>Alkalmazzon szabályt minden csomópontra</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="383"/> + <source>To this IP / Network</source> + <translation>Erre az IP-címre vagy a hálózatra</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="138"/> + <source>/path/to/executable, .*/bin/executable[0-9\.]+$, ...</source> + <translation type="obsolete">/útvonal/a/futtathatóhoz, .*/bináris/végrehajtható[0-9\.]+$, …</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="751"/> + <source>Action</source> + <translation>Művelet</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="293"/> + <source>To this port</source> + <translation>Erre a kikötőre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="583"/> + <source>To this list of domains</source> + <translation>Ehhez a tartománylistához</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="440"/> + <source>You can specify a single IP: +- 192.168.1.1 + +or a regular expression: +- 192\.168\.1\.[0-9]+ + +multiple IPs: +- ^(192\.168\.1\.1|172\.16\.0\.1)$ + +You can also specify a subnet: +- 192.168.1.0/24 + +Note: Commas or spaces are not allowed to separate IPs or networks.</source> + <translation>Megadhat egyetlen IP-címet: +- 192.168.1.1 + +vagy reguláris kifejezés: +- 192\.168\.1\.[0-9]+ + +több IP-cím: +- ^(192\.168\.1\.1|172\.16\.0\.1)$ + +Megadhat alhálózatot is: +- 192.168.1.0/24 + +Megjegyzés: Vesszőkkel vagy szóközökkel nem szabad elválasztani az IP-címeket vagy a hálózatokat.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="459"/> + <source>LAN</source> + <translation>Helyi hálózat</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="464"/> + <source>127.0.0.0/8</source> + <translation>127.0.0.0/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="469"/> + <source>192.168.0.0/24</source> + <translation>192.168.0.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="474"/> + <source>192.168.1.0/24</source> + <translation>192.168.1.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="479"/> + <source>192.168.2.0/24</source> + <translation>192.168.2.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="484"/> + <source>192.168.0.0/16</source> + <translation>192.168.0.0/16</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="489"/> + <source>169.254.0.0/16</source> + <translation>169.254.0.0/16</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="494"/> + <source>172.16.0.0/12</source> + <translation>172.16.0.0/12</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="499"/> + <source>10.0.0.0/8</source> + <translation>10.0.0.0/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="504"/> + <source>::1/128</source> + <translation>::1/128</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="509"/> + <source>fc00::/7</source> + <translation>fc00::/7</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="514"/> + <source>ff00::/8</source> + <translation>ff00::/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="519"/> + <source>fe80::/10</source> + <translation>fe80::/10</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="524"/> + <source>fd00::/8</source> + <translation>fd00::/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="369"/> + <source><html><head/><body><p>You can specify multiple ports using regular expressions:</p><p><br/></p><p>- 53, 80 or 443:</p><p>^(53|80|443)$</p><p><br/></p><p>- 53, 443 or 5551, 5552, 5553, etc:</p><p>^(53|443|555[0-9])$</p></body></html></source> + <translation><html><head/><body><p>Több kikötők megadhatók reguláris kifejezések használatával:</p><p><br/></p><p>- 53, 80 vagy 443:</p><p>^(53|80|443)$</p><p><br/></p><p>- 53, 443 vagy 5551, 5552, 5553, stb:</p><p>^(53|443|555[0-9])$</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="792"/> + <source>once</source> + <translation>egyszer</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="797"/> + <source>30s</source> + <translation>30 másodperc</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="802"/> + <source>5m</source> + <translation>5 perc</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="807"/> + <source>15m</source> + <translation>15 perc</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="812"/> + <source>30m</source> + <translation>30 perc</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="817"/> + <source>1h</source> + <translation>1 óra</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="822"/> + <source>until reboot</source> + <translation>újraindításig</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="827"/> + <source>always</source> + <translation>mindig</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="419"/> + <source>Commas or spaces are not allowed to specify multiple domains. + +Use regular expressions instead: +.*(opensnitch|duckduckgo).com +.*\.google.com + +or a single domain: +www.gnu.org - it'll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ... +gnu.org - it'll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source> + <translation>Vesszők vagy szóközök nem engedélyezhetnek több tartomány megadását. + +Használjon helyette reguláris kifejezéseket: +.*(opensnitch|duckduckgo).com +.*\.google.com + +vagy egyetlen tartomány: +www.gnu.org - csak a www.gnu.org, nem az ftp.gnu.org, a www2.gnu.org, … +gnu.org - csak a gnu.org-nak fog megfelelni, nem a www.gnu.org-nak, nem az ftp.gnu.org-nak, …</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="430"/> + <source>www.domain.org, .*\.domain.org</source> + <translation>www.tartomány.hu, .*\.tartomány.hu</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="300"/> + <source>To this host</source> + <translation>Erre az állomásra</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="784"/> + <source>Duration</source> + <translation>Időtartam</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="323"/> + <source><html><head/><body><p>Only TCP, UDP or UDPLITE are allowed</p><p>You can use regexp, i.e.: ^(TCP|UDP)$</p></body></html></source> + <translation><html><head/><body><p>Csak TCP, UDP vagy UDPLITE engedélyezett</p><p>Használhatja a reguláris kifejezést, azaz: ^(TCP|UDP)$</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="333"/> + <source>TCP</source> + <translation>TCP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="338"/> + <source>UDP</source> + <translation>UDP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="343"/> + <source>UDPLITE</source> + <translation>UDPLITE</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="348"/> + <source>TCP6</source> + <translation>TCP6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="353"/> + <source>UDP6</source> + <translation>UDP6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="358"/> + <source>UDPLITE6</source> + <translation>UDPLITE6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="376"/> + <source>Protocol</source> + <translation>Protokoll</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="206"/> + <source>From this executable</source> + <translation>Ebből a futtatható fájlból</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="843"/> + <source>Deny</source> + <translation>Megtagadás</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="877"/> + <source>Allow</source> + <translation>Engedélyezés</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="239"/> + <source>From this command line</source> + <translation>Ebből a parancssorból</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="169"/> + <source>From this user ID</source> + <translation>Ebből a felhasználói azonosítóból</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="539"/> + <source><html><head/><body><p>Select a directory with lists of domains to block or allow.</p><p>Put inside that directory files with any extension containing lists of domains.</p><p><br/>The format of each entry of a list is as follow (hosts format):</p><p>127.0.0.1 www.domain.com</p><p>or </p><p>0.0.0.0 www.domain.com</p></body></html></source> + <translation type="obsolete"><html><head/><body><p>Válasszon egy címtárat a letiltáshoz vagy engedélyezéshez szükséges tartománylistákkal.</p><p>Helyezze be a címtár fájlokat bármilyen kiterjesztéssel, amely tartalmazza a tartományok listáit.</p><p><br/>A lista minden bejegyzésének formátuma a következő (állomás formátum): </p><p>127.0.0.1 www.tartomány.hu</p><p>vagy </p><p>0.0.0.0 www.tartomány.hu</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="899"/> + <source>Name</source> + <translation>Név</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="22"/> + <source>Enable</source> + <translation>Engedélyezés</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="706"/> + <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them. + +000-allow-localhost +001-deny-broadcast +...</source> + <translation>A szabályokat ábécé sorrendben ellenőrzik, így azok prioritása szerint ennek megfelelően nevezheti meg őket. + +000-helyi-állomás-engedélyezése +001-közvetítés-tagadása +…</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="713"/> + <source>leave blank to autocreate</source> + <translation>hagyja üresen az önműködő létrehozáshoz</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="46"/> + <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one. + +You must name the rule in such manner that it'll be checked first, because they're checked in alphabetical order. For example: + +[x] Priority - 000-priority-rule +[ ] Priority - 001-less-priority-rule</source> + <translation>Ha be van jelölve, akkor ez a szabály elsőbbséget élvez a többi szabálygal szemben. Ez után más szabályokat nem fogunk ellenőrizni. + +A szabályt úgy kell megneveznie, hogy először ellenőrizni fogják, mert betűrendben ellenőrzik. Például: + +[x] Elsőbbség - 000-elsőbbségi-szabály +[ ] Elsőbbség - 001-kevésbé-elsőbbségi-szabály</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="54"/> + <source>Priority rule</source> + <translation>Elsőbbségi szabály</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="74"/> + <source><html><head/><body><p>By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.<br/></p><p>If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.</p></body></html></source> + <translation><html><head/><body><p>Alapértelmezés szerint a szabályok mezője nem különbözteti meg a kis- és nagybetűket, azaz ha egy folyamat megpróbálja elérni a gOOgle.CoM tartományt, és van egy szabályod, amelyet meg kell tagadni a .*google.com tartomány, a kapcsolat letiltva lesz.<br/></p><p>Ha bejelöli ezt a jelölőnégyzetet, meg kell adnia azt a pontos karakterláncot (tartomány, futtatható fájl, parancssor), amelyet szűrni szeretne.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="77"/> + <source>Case-sensitive</source> + <translation>Kis- és nagybetűk felismerése</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="163"/> + <source>Applications</source> + <translation>Alkalmazások</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="216"/> + <source><html><head/><body><p>This field will only match the executable path. It is not modifiable by the user.<br/></p><p>You can use regular expressions to deny executions from /tmp for example:<br/></p><p>^/tmp/.*$</p></body></html></source> + <translation><html><head/><body><p>Ez a mező csak a végrehajtható elérési úttal fog megegyezni. A felhasználó nem módosíthatja.<br/></p><p>Szabványos kifejezésekkel tilthatja meg a /tmp fájl végrehajtását, például:<br/></p><p>^/tmp/.*$</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="229"/> + <source><html><head/><body><p>This field will contain and match the command line that was executed by the user.<br/></p><p>If the user typed the command, only the command will appear:</p><p>telnet 1.2.3.4<br/></p><p>If the user typed the absolute or relative path to the command, that is what will appear:</p><p>/usr/bin/telnet 1.2.3.4</p><p>../../../usr/bin/telnet 1.2.3.4</p></body></html></source> + <translation><html><head/><body><p>Ez a mező tartalmazza a felhasználó által végrehajtott parancssort, és megegyezik vele.<br/></p><p>Ha a felhasználó beírta a parancsot, csak a parancsot megjelenik:</p><p>telnet 1.2.3.4<br/></p><p>Ha a felhasználó beírta a parancs abszolút vagy relatív elérési útját, akkor ez fog megjelenni:</p><p> >/usr/bin/telnet 1.2.3.4</p><p>../../../usr/bin/telnet 1.2.3.4</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="246"/> + <source>From this PID</source> + <translation>Ebből a folyamatazonosítóból</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="287"/> + <source>Network</source> + <translation>Hálózat</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="536"/> + <source>List of domains/IPs</source> + <translation>Tartományok/IP-címek listája</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="542"/> + <source>To this list of network ranges</source> + <translation>Ehhez a hálózati tartományok listájához</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="549"/> + <source>To this list of IPs</source> + <translation>Ehhez az IP-címlistához</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="574"/> + <source><html><head/><body><p>Select a directory with files containing list of IPs to block or allow:</p><p>1.2.3.4.5</p><p>1.2.3.4.6</p><p>.</p><p>etc.</p><p>One IP per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation><html><head/><body><p>Válasszon ki egy könyvtárat a tiltandó vagy engedélyezendő IP-címek listáját tartalmazó fájlokkal:</p><p>1.2.3.4.5</p><p>1.2.3.4. 6</p><p>.</p><p>stb.</p><p>Soronként egy IP-cím. Az üres sorokat vagy a # karakterrel kezdődő sorokat figyelmen kívül hagyja.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="608"/> + <source><html><head/><body><p>Select a directory with files containing list of network ranges to block or allow:</p><p>1.2.3.0/24</p><p>80.34.56.0/20</p><p>.</p><p>etc.<br/></p><p>One Network Range per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation><html><head/><body><p>Válasszon ki egy könyvtárat a tiltandó vagy engedélyezni kívánt hálózati tartományok listáját tartalmazó fájlokkal:</p><p>1.2.3.0/24</p><p>80.34.56.0 /20</p><p>.</p><p>stb.<br/></p><p>Egy hálózati tartomány soronként. Az üres sorokat vagy a # karakterrel kezdődő sorokat figyelmen kívül hagyja.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="636"/> + <source><html><head/><body><p>Select a directory with lists of domains to block or allow.</p><p>Put inside that directory files with any extension containing lists of domains.</p><p><br/>The format of each entry of a list is as follow (hosts format):</p><p>127.0.0.1 www.domain.com</p><p>or </p><p>0.0.0.0 www.domain.com</p><p>Empty lines or started with # are ignored.</p></body></html></source> + <translation><html><head/><body><p>Válasszon ki egy könyvtárat a tiltandó vagy engedélyezni kívánt tartományok listájával.</p><p>A könyvtárba helyezzen be olyan fájlokat, amelyek a tartomány listáit tartalmazzák.</p><p><br/>A lista minden egyes bejegyzésének formátuma a következő (gazdaformátum):</p><p>127.0.0.1 www.tartomány.com</p><p>vagy </p><p>0.0.0.0 www.tartomány.com</p><p>Az üres sorokat vagy a # karakterrel kezdődő sorokat figyelmen kívül hagyja.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="651"/> + <source>To this list of domains +(regular expressions)</source> + <translation>Ehhez a tartománylistához +(szabályos kifejezések)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="677"/> + <source><html><head/><body><p>Select a directory with files containing regular expressions of domains to block or allow:</p><p>.*\.example\.com</p><p>You can also use a domain as is: &quot;example.com&quot; , and it'll match whatever.example.com, whatever.example.com.localdomain, etc.</p><p>One domain per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation><html><head/><body><p>Válasszon ki egy könyvtárat a tiltandó vagy engedélyezendő tartományok szabványos kifejezéseit tartalmazó fájlokkal:</p><p>.*\.example\.com</p><p> Használhat olyan tartományt is, amilyen: &quot;example.com&quot;, és egyezik a bármi.példa.com, bármi.példa.com.helyitartomány stb. oldallal.</p><p>Soronként egy tartomány. Az üres sorokat vagy a # karakterrel kezdődő sorokat figyelmen kívül hagyja.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="857"/> + <source>Reject</source> + <translation>Elutasítás</translation> + </message> +</context> +<context> + <name>StatsDialog</name> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="34"/> + <source>OpenSnitch Network Statistics</source> + <translation>OpenSnitch hálózati statisztika</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="290"/> + <source>Save to CSV.</source> + <translation>Mentés CSV-fájlként…</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="300"/> + <source>Ctrl+S</source> + <translation>Ctrl+S</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="351"/> + <source>Create a new rule</source> + <translation>Új szabály létrehozása</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="381"/> + <source><html><head/><body><p><span style=" font-size:11pt; font-weight:600;">hostname - 192.168.1.1</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-size:11pt; font-weight:600;">állomásnév - 192.168.1.1</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="420"/> + <source>Status</source> + <translation>Állapot</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1665"/> + <source>-</source> + <translation>-</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="464"/> + <source>Start or Stop interception</source> + <translation>Adatelérés indítása vagy leállítása</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="509"/> + <source>Events</source> + <translation>Események</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="94"/> + <source>Filter</source> + <translation>Szűrő</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="107"/> + <source>Allow</source> + <translation>Engedélyezés</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="116"/> + <source>Deny</source> + <translation>Megtagadás</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="143"/> + <source>Ex.: firefox</source> + <translation>Például: Firefox</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="199"/> + <source>50</source> + <translation>50</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="204"/> + <source>100</source> + <translation>100</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="209"/> + <source>200</source> + <translation>200</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="214"/> + <source>300</source> + <translation>300</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="227"/> + <source>Delete all intercepted events</source> + <translation>Az összes elfogott esemény törlése</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="748"/> + <source>Nodes</source> + <translation>Csomópontok</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="554"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on the Addr column to view details of a node)</span></p></body></html></source> + <translation type="obsolete"><html><head/><body><p><span style=" font-size:7pt;">(kattintson duplán a Cím oszlopra a csomópont részleteinek megtekintéséhez)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1569"/> + <source>Rules</source> + <translation>Szabályok</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="857"/> + <source>enable</source> + <translation>engedélyezés</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="864"/> + <source>Edit rule</source> + <translation>Szabály szerkesztése</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="878"/> + <source>Delete rule</source> + <translation>Szabály törlése</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="699"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on a row to view details of a rule)</span></p></body></html></source> + <translation type="obsolete"><html><head/><body><p><span style=" font-size:7pt;">(kattintson duplán egy sorra a szabály részleteinek megtekintéséhez)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="692"/> + <source>search rule name</source> + <translation type="obsolete">szabálynév keresése</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="704"/> + <source>Application rules</source> + <translation>Alkalmazási szabályok</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="806"/> + <source>Permanent</source> + <translation>Állandó</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="815"/> + <source>Temporary</source> + <translation>Ideiglenes</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="933"/> + <source>Hosts</source> + <translation>Gazdagépek</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1364"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click to view details of an item)</span></p></body></html></source> + <translation type="obsolete"><html><head/><body><p><span style=" font-size:7pt;">(kattintson duplán az elem részleteinek megtekintéséhez)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="926"/> + <source>Delete all intercepted hosts</source> + <translation type="obsolete">Az összes elfogott gazdagép törlése</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1020"/> + <source>Applications</source> + <translation>Alkalmazások</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1051"/> + <source>Delete all intercepted applications</source> + <translation type="obsolete">Az összes elfogott alkalmazás törlése</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1127"/> + <source>Addresses</source> + <translation>Címek</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1159"/> + <source>Delete all intercepted addresses</source> + <translation type="obsolete">Az összes elfogott cím törlése</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1214"/> + <source>Ports</source> + <translation>Kikötők</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1261"/> + <source>Delete all intercepted ports</source> + <translation type="obsolete">Az összes elfogott kikötő törlése</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1298"/> + <source>Users</source> + <translation>Felhasználók</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1371"/> + <source>Delete all intercepted users</source> + <translation type="obsolete">Az összes elfogott felhasználó törlése</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1404"/> + <source>Connections</source> + <translation>Kapcsolatok</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1459"/> + <source>Dropped</source> + <translation>Elvetve</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1514"/> + <source>Uptime</source> + <translation>Hasznos üzemidő</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1639"/> + <source>Version</source> + <translation>Változat</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="912"/> + <source>Delete connections that matched this rule</source> + <translation>Kapcsolat törlése amelyek megfelelnek ennek a szabálynak</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="797"/> + <source>All applications</source> + <translation>Minden alkalmazás</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="125"/> + <source>Reject</source> + <translation>Elutasítás</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="177"/> + <source>0</source> + <translation>0</translation> + </message> +</context> +<context> + <name>contextual_menu</name> + <message> + <location filename="../../../opensnitch/service.py" line="43"/> + <source>Statistics</source> + <translation>Statisztika</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="44"/> + <source>Enable</source> + <translation>Engedélyezés</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="45"/> + <source>Disable</source> + <translation>Letiltás</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="46"/> + <source>Help</source> + <translation>Súgó</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="47"/> + <source>Close</source> + <translation>Bezárás</translation> + </message> +</context> +<context> + <name>notifications</name> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="518"/> + <source>System notifications are not available, you need to install python3-notify2.</source> + <translation>A rendszerértesítések nem érhetők el, telepítenie kell a python3-notify2-t.</translation> + </message> +</context> +<context> + <name>popups</name> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="52"/> + <source>until reboot</source> + <translation>újraindításig</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="54"/> + <source>forever</source> + <translation>örökre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="91"/> + <source>Allow</source> + <translation>Engedélyezés</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="92"/> + <source>Deny</source> + <translation>Megtagadás</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="285"/> + <source>Outgoing connection</source> + <translation>Kimenő kapcsolat</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="290"/> + <source>Process launched from:</source> + <translation>Folyamat innen indult:</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="321"/> + <source>from this executable</source> + <translation>ettől a végrehajtható fájlból</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="325"/> + <source>from this command line</source> + <translation>erről a parancssorról</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="327"/> + <source>to port {0}</source> + <translation>kikötőig: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="390"/> + <source>to {0}</source> + <translation>eddig: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="330"/> + <source>from user {0}</source> + <translation>a(z) {0} felhasználótól</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="347"/> + <source>to {0}.*</source> + <translation>eddig: {0}.*</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="400"/> + <source>to *.{0}</source> + <translation>eddig: *.{0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="378"/> + <source>to *{0}</source> + <translation type="obsolete">eddig: *{0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="434"/> + <source><b>Remote</b> process %s running on <b>%s</b></source> + <translation>A(z) %s <b>távoli</b> folyamat fut a(z) <b>%s</b>-n</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="438"/> + <source>is connecting to <b>%s</b> on %s port %d</source> + <translation>csatlakozik <b>%s</b>-hoz a %s-kikötőn %d</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="444"/> + <source>is attempting to resolve <b>%s</b> via %s, %s port %d</source> + <translation>megpróbálja megoldani a(z) <b>%s</b> problémát a(z) %s segítségével, %s-kikötő %d</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="334"/> + <source>from this PID</source> + <translation>ebből a folyamatazonosítóból</translation> + </message> + <message> + <location filename="../../../opensnitch/notifications.py" line="105"/> + <source>New outgoing connection</source> + <translation>Új kimenő kapcsolat</translation> + </message> +</context> +<context> + <name>preferences</name> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="299"/> + <source>Exception saving config: {0}</source> + <translation>Beállítás mentése kivétele: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="323"/> + <source>Warning</source> + <translation>Figyelmeztetés</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="323"/> + <source>You must select a file for the database<br>or choose "In memory" type.</source> + <translation>Válasszon egy fájlt az adatbázishoz<br>vagy válassza a „Memóriabeli” típust.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="329"/> + <source>DB type changed</source> + <translation>DB típusa megváltozott</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="329"/> + <source>Restart the GUI in order effects to take effect</source> + <translation>Indítsa újra a grafikus felhasználói felületet, hogy a hatások életbe léphessenek</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="389"/> + <source>Applying configuration on {0} ...</source> + <translation>Beállítások alkalmazása: {0}…</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="208"/> + <source>Server address can not be empty</source> + <translation>Kiszolgáló címe nem lehet üres</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="238"/> + <source>Error loading {0} configuration</source> + <translation>Hiba történt a(z) {0}-beállítás betöltésekor</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="448"/> + <source>Configuration applied.</source> + <translation>Beállítás alkalmazva.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="450"/> + <source>Error applying configuration: {0}</source> + <translation>Hiba a beállítás alkalmazásakor: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="480"/> + <source>Hover the mouse over the texts to display the help<br><br>Don't forget to visit the wiki: <a href="{0}">{0}</a></source> + <translation>A súgó megjelenítéséhez vigye az egeret a szövegek fölé<br><br>Emlékezzen meglátogatni a wikit: <a href="{0}">{0}</a></translation> + </message> +</context> +<context> + <name>proc_details</name> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="97"/> + <source><b>Error loading process information:</b> <br><br> + +</source> + <translation><b>Hiba a folyamatadatok betöltésekor:</b> <br><br> + +</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="116"/> + <source><b>Error stopping monitoring process:</b><br><br></source> + <translation><b>Hiba a megfigyelési folyamat leállításakor:</b><br><br></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="156"/> + <source>loading...</source> + <translation>betöltés folyamatban…</translation> + </message> +</context> +<context> + <name>rules</name> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="162"/> + <source>There're no nodes connected.</source> + <translation>Nincsenek csomópontok csatlakoztatva.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="203"/> + <source>Rule applied.</source> + <translation>A szabály alkalmazva.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="205"/> + <source>Error applying rule: {0}</source> + <translation>Hiba a szabály alkalmazásakor: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="429"/> + <source><b>Error loading rule</b></source> + <translation><b>Hiba történt a szabály betöltésekor</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="527"/> + <source>protocol can not be empty, or uncheck it</source> + <translation>a protokoll nem lehet üres, és nem szüntetheti meg a kijelölést</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="541"/> + <source>Protocol regexp error</source> + <translation>Protokoll reguláris kifejezéshibája</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="545"/> + <source>process path can not be empty</source> + <translation>folyamat útvonal nem lehet üres</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="559"/> + <source>Process path regexp error</source> + <translation>Folyamat útvonala reguláris kifejezéshibája</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="563"/> + <source>command line can not be empty</source> + <translation>parancssor nem lehet üres</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="577"/> + <source>Command line regexp error</source> + <translation>Parancssor reguláris kifejezéshibája</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="581"/> + <source>Dest port can not be empty</source> + <translation>Célkikötő nem lehet üres</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="595"/> + <source>Dst port regexp error</source> + <translation>Célkikötő szabványos kifejezéshibája</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="599"/> + <source>Dest host can not be empty</source> + <translation>Célkikötő nem lehet üres</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="613"/> + <source>Dst host regexp error</source> + <translation>Célkikötő reguláris kifejezéshibája</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="617"/> + <source>Dest IP/Network can not be empty</source> + <translation>Cél IP-cím/hálózat nem lehet üres</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="639"/> + <source>Dst IP regexp error</source> + <translation>Cél IP-cím reguláris kifejezéshibája</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="651"/> + <source>User ID can not be empty</source> + <translation>Felhasználói azonosító nem lehet üres</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="665"/> + <source>User ID regexp error</source> + <translation>Felhasználói azonosító reguláris kifejezéshibája</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="739"/> + <source>Lists field cannot be empty</source> + <translation>Listamező nem lehet üres</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="741"/> + <source>Lists field must be a directory</source> + <translation>Listmezők könyvtárnak kell lennie</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="777"/> + <source><b>Rule not supported</b></source> + <translation><b>A szabály nem támogatott</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="179"/> + <source>There's already a rule with this name.</source> + <translation>Már van egy szabály ezzel a névvel.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="669"/> + <source>PID field can not be empty</source> + <translation>Folyamatazonosító mező nem lehet üres</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="683"/> + <source>PID field regexp error</source> + <translation>Folyamatazonosító mező szabványos kifejezési hiba</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="764"/> + <source>Select at least one field.</source> + <translation>Jelöljön ki legalább egy mezőt.</translation> + </message> +</context> +<context> + <name>stats</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <translation type="obsolete">Név</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="294"/> + <source>Address</source> + <translation type="obsolete">Cím</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="176"/> + <source>Status</source> + <translation type="obsolete">Állapot</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="177"/> + <source>Hostname</source> + <translation type="obsolete">Állomásnév</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="183"/> + <source>Version</source> + <translation type="obsolete">Változat</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="180"/> + <source>Rules</source> + <translation type="obsolete">Szabályok</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Time</source> + <translation type="obsolete">Idő</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Action</source> + <translation type="obsolete">Művelet</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Duration</source> + <translation type="obsolete">Időtartam</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Node</source> + <translation type="obsolete">Csomópont</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>Enabled</source> + <translation type="obsolete">Engedélyezve</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="253"/> + <source>Hits</source> + <translation>Találatok száma</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>Protocol</source> + <translation type="obsolete">Protokoll</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="310"/> + <source>Not running</source> + <translation>Nem fut</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="311"/> + <source>Disabled</source> + <translation>Letiltva</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="312"/> + <source>Running</source> + <translation>Futtatás</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="568"/> + <source>OpenSnitch Network Statistics {0}</source> + <translation>{0} OpenSnitch hálózati statisztika</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="570"/> + <source>OpenSnitch Network Statistics for {0}</source> + <translation>OpenSnitch hálózati statisztikák a következőhöz: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="921"/> + <source><b>Error:</b><br><br></source> + <comment>{0}</comment> + <translation><b>Hiba:</b><br><br></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="928"/> + <source>Warning:</source> + <translation>Figyelmeztetés:</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="717"/> + <source>Allow</source> + <translation>Engedélyezés</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="718"/> + <source>Deny</source> + <translation>Megtagadás</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="722"/> + <source>Always</source> + <translation>Mindig</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="723"/> + <source>Until reboot</source> + <translation>Újraindításig</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="731"/> + <source>Disable</source> + <translation>Letiltás</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="733"/> + <source>Enable</source> + <translation>Engedélyezés</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="736"/> + <source>Duplicate</source> + <translation>Másolás</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="737"/> + <source>Edit</source> + <translation>Szerkesztés</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="738"/> + <source>Delete</source> + <translation>Törlés</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="761"/> + <source> Your are about to delete this rule. </source> + <translation>Törölni készül ezt a szabályt.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1244"/> + <source> Are you sure?</source> + <translation>Biztos benne?</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="891"/> + <source>Rule not found by that name and node</source> + <translation>A szabály nem található ezen a néven és csomóponton</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1244"/> + <source> You are about to delete this rule. </source> + <translation>Törölni készül ezt a szabályt.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1837"/> + <source>Save as CSV</source> + <translation>Mentés CSV formátumban…</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="308"/> + <source>Rule</source> + <translation type="obsolete">Szabály</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <comment>xxxxx</comment> + <translation type="obsolete">Név</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Név</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="294"/> + <source>Address</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Cím</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="295"/> + <source>Status</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Állapot</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="296"/> + <source>Hostname</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Állomásnév</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="297"/> + <source>Version</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Változat</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="298"/> + <source>Rules</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Szabályok</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Time</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Idő</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Action</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Művelet</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Duration</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Időtartam</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Node</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Csomópont</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>Enabled</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Engedélyezve</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="304"/> + <source>Hits</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Találatok száma</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>Protocol</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Protokoll</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="308"/> + <source>Rule</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Szabály</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="392"/> + <source>Name</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Név</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="288"/> + <source>Address</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Cím</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="379"/> + <source>Status</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Állapot</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="380"/> + <source>Hostname</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Állomásnév</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="386"/> + <source>Version</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Változat</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="383"/> + <source>Rules</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Szabályok</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="390"/> + <source>Time</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Idő</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="395"/> + <source>Action</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Művelet</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="396"/> + <source>Duration</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Időtartam</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="391"/> + <source>Node</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Csomópont</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="393"/> + <source>Enabled</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Engedélyezve</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="405"/> + <source>Hits</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Találatok</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Protocol</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Protokoll</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Process</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Folyamat</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Destination</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Cél</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="306"/> + <source>Rule</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Szabály</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="307"/> + <source>UserID</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Felhasználóazonosító</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="377"/> + <source>LastConnection</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>LegutóbbiCsatlakozás</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Args</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Argumentumok</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>DstIP</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>CélIPcíme</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="304"/> + <source>DstHost</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Célállomásneve</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>DstPort</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Célkikötő</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="174"/> + <source>LastConnection</source> + <translation type="obsolete">LegutóbbiCsatlakozás</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="179"/> + <source>Uptime</source> + <translation type="obsolete">Hasznos üzemidő</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="181"/> + <source>Connections</source> + <translation type="obsolete">Kapcsolatok</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="182"/> + <source>Dropped</source> + <translation type="obsolete">Elvetve</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="252"/> + <source>What</source> + <translation>Mi</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="709"/> + <source>Apply to</source> + <translation>Alkalmazás a következőre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="719"/> + <source>Reject</source> + <translation>Elutasítás</translation> + </message> + <message> + <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="25"/> + <source>Network name</source> + <translation>Hálózatnév</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="378"/> + <source>Addr</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Cím</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="382"/> + <source>Uptime</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Hasznos üzemidő</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="384"/> + <source>Connections</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Kapcsolatok</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="385"/> + <source>Dropped</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Elvetve</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="404"/> + <source>What</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Mi</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="394"/> + <source>Precedence</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Sorrend</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="644"/> + <source>New node connected</source> + <translation>Új csomópont csatlakoztatva</translation> + </message> +</context> +</TS> diff --git a/ui/i18n/locales/ja_JP/opensnitch-ja_JP.ts b/ui/i18n/locales/ja_JP/opensnitch-ja_JP.ts new file mode 100644 index 0000000..ecd2e15 --- /dev/null +++ b/ui/i18n/locales/ja_JP/opensnitch-ja_JP.ts @@ -0,0 +1,2164 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS><TS version="2.0" language="ja_JP" sourcelanguage=""> +<context> + <name>Dialog</name> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="34"/> + <source>opensnitch-qt</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="299"/> + <source>User ID</source> + <translation type="unfinished">ユーザーID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="333"/> + <source><html><head/><body><p><span style=" font-weight:600;">Executed from</span></p></body></html></source> + <translation type="unfinished"><html><head/><body><p><span style=" font-weight:600;">実行元</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="630"/> + <source>TextLabel</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="426"/> + <source>Source IP</source> + <translation type="unfinished">送信元のIP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="449"/> + <source>Process ID</source> + <translation type="unfinished">プロセスID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="582"/> + <source>Destination IP</source> + <translation type="unfinished">宛先IP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="605"/> + <source>Dst Port</source> + <translation type="unfinished">宛先ポート</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="679"/> + <source>from this executable</source> + <translation type="unfinished">この実行ファイルを</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="684"/> + <source>from this command line</source> + <translation type="unfinished">このコマンドラインを</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="689"/> + <source>this destination port</source> + <translation type="unfinished">この宛先ポートに対して</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="694"/> + <source>this user</source> + <translation type="unfinished">このユーザーを</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="699"/> + <source>this destination ip</source> + <translation type="unfinished">この宛先IPに対して</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="728"/> + <source>once</source> + <translation type="unfinished">一度のみ</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="733"/> + <source>30s</source> + <translation type="unfinished">30秒間</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="738"/> + <source>5m</source> + <translation type="unfinished">5分間</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="743"/> + <source>15m</source> + <translation type="unfinished">15分間</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="748"/> + <source>30m</source> + <translation type="unfinished">30分間</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="753"/> + <source>1h</source> + <translation type="unfinished">1時間</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="758"/> + <source>until reboot</source> + <translation type="unfinished">再起動するまで</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="763"/> + <source>forever</source> + <translation type="unfinished">永久に</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="789"/> + <source>Deny</source> + <translation type="unfinished">拒否する</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="818"/> + <source>Allow</source> + <translation type="unfinished">許可する</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="847"/> + <source>+</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="704"/> + <source>from this PID</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>New node connected</name> +</context> +<context> + <name>PreferencesDialog</name> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="14"/> + <source>Preferences</source> + <translation type="unfinished">設定</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="472"/> + <source>UI</source> + <translation type="unfinished">UI</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="454"/> + <source>Default timeout</source> + <translation type="unfinished">ダイアログの表示時間</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="331"/> + <source>Pop-up default duration</source> + <translation type="unfinished">ポップアップ時に選択される規定のルールの有効期間</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="754"/> + <source>Default duration</source> + <translation type="unfinished">既定のルール有効期間</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="162"/> + <source>Pop-up default action</source> + <translation type="obsolete">ポップアップ時に選択される規定のアクション</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="483"/> + <source>Default action</source> + <translation type="obsolete">既定のアクション</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="314"/> + <source>Default target</source> + <translation type="unfinished">既定のターゲット</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="170"/> + <source>center</source> + <translation type="unfinished">中央</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="175"/> + <source>top right</source> + <translation type="unfinished">右上部</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="180"/> + <source>bottom right</source> + <translation type="unfinished">右下部</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="185"/> + <source>top left</source> + <translation type="unfinished">左上部</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="190"/> + <source>bottom left</source> + <translation type="unfinished">左下部</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="167"/> + <source>Prompt dialog default position on screen</source> + <translation type="obsolete">既定のダイアログ表示位置</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="274"/> + <source>by executable</source> + <translation type="unfinished">実行ファイル</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="279"/> + <source>by command line</source> + <translation type="unfinished">コマンドライン</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="284"/> + <source>by destination port</source> + <translation type="unfinished">宛先ポート</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="289"/> + <source>by destination ip</source> + <translation type="unfinished">宛先IP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="294"/> + <source>by user id</source> + <translation type="unfinished">ユーザーID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="863"/> + <source>once</source> + <translation type="unfinished">一度のみ</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="210"/> + <source>30s</source> + <translation type="unfinished">30秒間</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="215"/> + <source>5m</source> + <translation type="unfinished">5分間</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="220"/> + <source>15m</source> + <translation type="unfinished">15分間</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="225"/> + <source>30m</source> + <translation type="unfinished">30分間</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="230"/> + <source>1h</source> + <translation type="unfinished">1時間</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="235"/> + <source>until reboot</source> + <translation type="unfinished">再起動するまで</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="240"/> + <source>forever</source> + <translation type="unfinished">永久に</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="905"/> + <source>deny</source> + <translation type="unfinished">拒否</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="914"/> + <source>allow</source> + <translation type="unfinished">許可</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="406"/> + <source>Disable pop-ups, only display an alert</source> + <translation type="obsolete">ポップアップを無効にして通知のみ表示</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="711"/> + <source>Nodes</source> + <translation type="unfinished">ノード</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="717"/> + <source>Process monitor method</source> + <translation type="unfinished">プロセス監視方式</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="751"/> + <source><html><head/><body><p>The default duration will take place when there's no UI connected.</p></body></html></source> + <translation type="unfinished"><html><head/><body><p>規定のルール有効期間は、UIが接続されていないときに使用されます。</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="881"/> + <source><html><head/><body><p>Address of the node.</p><p>Default: unix:///tmp/osui.sock (unix:// is mandatory if it's a Unix socket)</p><p>It can also be an IP address with the port: 127.0.0.1:50051</p></body></html></source> + <translation type="unfinished"><html><head/><body><p>ノードのアドレス</p><p>標準: unix:///tmp/osui.sock (Unixソケットの場合はunix://が必須)</p><p>このようにIPアドレスとポートを指定することもできます。127.0.0.1:50051</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="884"/> + <source>Address</source> + <translation type="unfinished">アドレス</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1024"/> + <source>Default log level</source> + <translation type="unfinished">既定のログレベル</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="932"/> + <source>Version</source> + <translation type="unfinished">バージョン</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="790"/> + <source><html><head/><body><p>The default action will take place when there's no UI connected.</p></body></html></source> + <translation type="unfinished"><html><head/><body><p>規定のアクションは、UIが接続されていないときに使用されます。</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="671"/> + <source>proc</source> + <translation type="obsolete">proc</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="681"/> + <source>audit</source> + <translation type="obsolete">audit</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="686"/> + <source>ftrace</source> + <translation type="obsolete">ftrace</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="734"/> + <source><html><head/><body><p>Log file to write logs.<br/></p><p>/dev/stdout will print logs to the standard output.</p></body></html></source> + <translation type="unfinished"><html><head/><body><p>ファイルにログを記録します<br/></p><p>/dev/stdoutにすると標準出力にログを出力します</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="737"/> + <source>Log file</source> + <translation type="unfinished">ログファイル</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="778"/> + <source>DEBUG</source> + <translation type="obsolete">DEBUG</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="783"/> + <source>INFO</source> + <translation type="obsolete">INFO</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="788"/> + <source>IMPORTANT</source> + <translation type="obsolete">IMPORTANT</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="793"/> + <source>WARNING</source> + <translation type="obsolete">WARNING</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="798"/> + <source>ERROR</source> + <translation type="obsolete">ERROR</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="803"/> + <source>FATAL</source> + <translation type="obsolete">FATAL</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="578"/> + <source><html><head/><body><p>If checked, opensnitch will prompt you to allow or deny connections that don't have an asocciated PID, due to several reasons.</p><p>The pop-up dialog will only contain information about the network connection.</p></body></html></source> + <translation type="obsolete"><html><head/><body><p>有効にした場合、opensnitchは、関連したPIDを持たない接続を許可するか拒否するかを確認します。</p><p>ポップアップダイアログには、ネットワーク接続に関する情報のみが表示されます。</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="581"/> + <source>Intercept Unknown Connections</source> + <translation type="obsolete">不明なプロセスを検証</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="809"/> + <source>HostName</source> + <translation type="unfinished">ホスト名</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="983"/> + <source>unix:///tmp/osui.sock</source> + <translation type="unfinished">unix:///tmp/osui.sock</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="868"/> + <source>until restart</source> + <translation type="unfinished">再起動するまで</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="873"/> + <source>always</source> + <translation type="unfinished">常に</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="995"/> + <source>/var/log/opensnitchd.log</source> + <translation type="unfinished">/var/log/opensnitchd.log</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1000"/> + <source>/dev/stdout</source> + <translation type="unfinished">/dev/stdout</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="767"/> + <source>Apply configuration to all nodes</source> + <translation type="unfinished">全てのノードに設定を反映</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1039"/> + <source>Database</source> + <translation type="unfinished">データベース</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1093"/> + <source>Database type</source> + <translation type="unfinished">データベース方式</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1100"/> + <source>Select</source> + <translation type="unfinished">参照</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1074"/> + <source>In memory</source> + <translation type="unfinished">メモリ内</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1079"/> + <source>File</source> + <translation type="unfinished">ファイル</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1345"/> + <source>Close</source> + <translation type="unfinished">閉じる</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1356"/> + <source>Apply</source> + <translation type="unfinished">適用</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1367"/> + <source>Save</source> + <translation type="unfinished">保存</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="83"/> + <source>Pop-ups default options</source> + <translation type="obsolete">ポップアップの規定のアクション</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="367"/> + <source>Pop-ups default position on screen</source> + <translation type="obsolete">規定のポップアップ表示位置</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="347"/> + <source>The advanced view allows you to easily select multiple fields to filter connections</source> + <translation type="unfinished">詳細表示では、接続をフィルタリングするために複数のフィールドを簡単に選択できます</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="350"/> + <source>Show advanced view by default</source> + <translation type="unfinished">標準で詳細表示を有効にする</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="665"/> + <source>Action</source> + <translation type="unfinished">アクション</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="366"/> + <source><html><head/><body><p>If checked, the pop-ups will be displayed with the advanced view active.</p></body></html></source> + <translation type="unfinished"><html><head/><body><p>有効にすると、詳細表示がアクティブな状態でポップアップが表示されます。</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="334"/> + <source>Duration</source> + <translation type="unfinished">ルールの有効期間</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="254"/> + <source><html><head/><body><p>By default when a new pop-up appears, in its simplest form, you'll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).</p><p>With these options, you can choose multiple fields to filter connections for.</p></body></html></source> + <translation type="unfinished"><html><head/><body><p>ポップアップの表示時、標準では接続の1つのプロパティ (実行可能ファイル、ポート、IP など) によって接続またはアプリケーションをフィルタリングできます。</p><p>これらのオプションを使用すると、接続をフィルタリングする際、複数のフィールドを選択できます。</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="257"/> + <source>Filter connections also by:</source> + <translation>次を使用して通信をフィルタ</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="110"/> + <source>If checked, this field will be selected when a pop-up is displayed</source> + <translation type="unfinished">有効にすると、ポップアップが表示されたときに、このフィールドが選択されます</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="81"/> + <source>User ID</source> + <translation type="unfinished">ユーザーID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="97"/> + <source>Destination port</source> + <translation type="unfinished">宛先ポート</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="113"/> + <source>Destination IP</source> + <translation type="unfinished">宛先IP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="451"/> + <source><html><head/><body><p>This timeout is the countdown you see when a pop-up dialog is shown.</p><p>If the pop-up is not answered, the default options will be applied.</p></body></html></source> + <translation type="unfinished"><html><head/><body><p>このタイムアウトは、ポップアップダイアログの表示時間のカウントダウンです。</p><p>ポップアップに回答しない場合、このオプションが適用されます。</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="676"/> + <source>ebpf</source> + <translation type="obsolete">ebpf</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="150"/> + <source><html><head/><body><p>Pop-up default action.</p><p>When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.</p><p><br/></p><p>While a pop-up is asking the user to allow or deny a connection:</p><p>1. new outgoing connections are denied.</p><p>2. known connections are allowed or denied based on the rules defined by the user.</p></body></html></source> + <translation type="unfinished"><html><head/><body><p>ポップアップの規定のアクション</p><p>When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.</p><p><br/></p><p>While a pop-up is asking the user to allow or deny a connection:</p><p>1. new outgoing connections are denied.</p><p>2. known connections are allowed or denied based on the rules defined by the user.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="793"/> + <source>Default action when the GUI is disconnected</source> + <translation type="unfinished">GUI未接続時の規定のアクション</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="894"/> + <source>Debug invalid connections</source> + <translation type="unfinished">無効な接続をデバッグ</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="39"/> + <source>Pop-ups</source> + <translation type="unfinished">ポップアップ</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="64"/> + <source>Default options</source> + <translation type="unfinished">規定のオプション</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="321"/> + <source>Default position on screen</source> + <translation type="unfinished">規定の表示位置</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="479"/> + <source>any temporary rules</source> + <translation type="unfinished">全ての一時ルール</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="492"/> + <source><html><head/><body><p>When this option is selected, the rules of the selected duration won't be added to the list of temporary rules in the GUI.</p><p><br/></p><p>Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="495"/> + <source>Don't save rules of duration</source> + <translation type="unfinished">ルールの有効期間を保持しない</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="601"/> + <source>Time</source> + <translation type="unfinished">時間</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="681"/> + <source>Destination</source> + <translation type="unfinished">宛先</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="649"/> + <source>Protocol</source> + <translation type="unfinished">プロトコル</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="697"/> + <source>Process</source> + <translation type="unfinished">プロセス</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="617"/> + <source>Rule</source> + <translation type="unfinished">ルール</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="633"/> + <source>Node</source> + <translation type="unfinished">ノード</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="589"/> + <source>Events tab columns</source> + <translation type="unfinished">イベントタブの項目</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="891"/> + <source><html><head/><body><p>If checked, OpenSnitch will prompt you to allow or deny connections that don't have an associated PID, due to several reasons, mostly due to bad state connections.</p><p>The pop-up dialog will only contain information about the network connection.</p><p>There're some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="299"/> + <source>by PID</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="461"/> + <source>Disable pop-ups, only display an notification</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="508"/> + <source>Desktop notifications</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="526"/> + <source>Use system notifications</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="542"/> + <source>Use Qt notifications</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="571"/> + <source>Test</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1178"/> + <source>minutes</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1204"/> + <source>Minutes between events purges</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1227"/> + <source>days</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1237"/> + <source>Maximum days of events to keep</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>ProcessDetailsDialog</name> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="14"/> + <source>Process details</source> + <translation type="unfinished">プロセス情報</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="61"/> + <source>loading...</source> + <translation type="unfinished">読み込み中...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="81"/> + <source>CWD: loading...</source> + <translation type="unfinished">CWD:-読み込み中...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="93"/> + <source>mem stats: loading...</source> + <translation type="unfinished">メモリ状態: 読み込み中...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="121"/> + <source>Status</source> + <translation type="unfinished">状態</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="135"/> + <source>Open files</source> + <translation type="unfinished">ファイルアクセス</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="149"/> + <source>I/O Statistics</source> + <translation type="unfinished">入出力の統計</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="163"/> + <source>Memory mapped files</source> + <translation type="unfinished">メモリ内データ</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="177"/> + <source>Stack</source> + <translation type="unfinished">スタック</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="191"/> + <source>Environment variables</source> + <translation type="unfinished">環境変数</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="210"/> + <source>Application pids</source> + <translation type="unfinished">プロセスID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="240"/> + <source>Start or stop monitoring this process</source> + <translation type="unfinished">プロセスの監視を開始/停止</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="256"/> + <source>Close</source> + <translation type="unfinished">閉じる</translation> + </message> +</context> +<context> + <name>RulesDialog</name> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="14"/> + <source>Rule</source> + <translation type="unfinished">ルール</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="118"/> + <source>Node</source> + <translation type="unfinished">ノード</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="141"/> + <source>Apply rule to all nodes</source> + <translation type="unfinished">全てのノードにルールを反映</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="383"/> + <source>To this IP / Network</source> + <translation type="unfinished">IP/ネットワーク</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="751"/> + <source>Action</source> + <translation type="unfinished">アクション</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="293"/> + <source>To this port</source> + <translation type="unfinished">ポート</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="583"/> + <source>To this list of domains</source> + <translation type="unfinished">ドメインリスト</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="440"/> + <source>You can specify a single IP: +- 192.168.1.1 + +or a regular expression: +- 192\.168\.1\.[0-9]+ + +multiple IPs: +- ^(192\.168\.1\.1|172\.16\.0\.1)$ + +You can also specify a subnet: +- 192.168.1.0/24 + +Note: Commas or spaces are not allowed to separate IPs or networks.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="459"/> + <source>LAN</source> + <translation type="unfinished">LAN</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="464"/> + <source>127.0.0.0/8</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="469"/> + <source>192.168.0.0/24</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="474"/> + <source>192.168.1.0/24</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="479"/> + <source>192.168.2.0/24</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="484"/> + <source>192.168.0.0/16</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="489"/> + <source>169.254.0.0/16</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="494"/> + <source>172.16.0.0/12</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="499"/> + <source>10.0.0.0/8</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="504"/> + <source>::1/128</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="509"/> + <source>fc00::/7</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="514"/> + <source>ff00::/8</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="519"/> + <source>fe80::/10</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="524"/> + <source>fd00::/8</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="369"/> + <source><html><head/><body><p>You can specify multiple ports using regular expressions:</p><p><br/></p><p>- 53, 80 or 443:</p><p>^(53|80|443)$</p><p><br/></p><p>- 53, 443 or 5551, 5552, 5553, etc:</p><p>^(53|443|555[0-9])$</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="792"/> + <source>once</source> + <translation type="unfinished">一度のみ</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="797"/> + <source>30s</source> + <translation type="unfinished">30秒間</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="802"/> + <source>5m</source> + <translation type="unfinished">5分間</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="807"/> + <source>15m</source> + <translation type="unfinished">15分間</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="812"/> + <source>30m</source> + <translation type="unfinished">30分間</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="817"/> + <source>1h</source> + <translation type="unfinished">1時間</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="822"/> + <source>until reboot</source> + <translation type="unfinished">再起動するまで</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="827"/> + <source>always</source> + <translation type="unfinished">常に</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="419"/> + <source>Commas or spaces are not allowed to specify multiple domains. + +Use regular expressions instead: +.*(opensnitch|duckduckgo).com +.*\.google.com + +or a single domain: +www.gnu.org - it'll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ... +gnu.org - it'll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="430"/> + <source>www.domain.org, .*\.domain.org</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="300"/> + <source>To this host</source> + <translation type="unfinished">ホスト</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="784"/> + <source>Duration</source> + <translation type="unfinished">有効期間</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="323"/> + <source><html><head/><body><p>Only TCP, UDP or UDPLITE are allowed</p><p>You can use regexp, i.e.: ^(TCP|UDP)$</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="333"/> + <source>TCP</source> + <translation type="unfinished">TCP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="338"/> + <source>UDP</source> + <translation type="unfinished">UDP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="343"/> + <source>UDPLITE</source> + <translation type="unfinished">UDPLITE</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="348"/> + <source>TCP6</source> + <translation type="unfinished">TCP6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="353"/> + <source>UDP6</source> + <translation type="unfinished">UDP6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="358"/> + <source>UDPLITE6</source> + <translation type="unfinished">UDPLITE6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="376"/> + <source>Protocol</source> + <translation type="unfinished">プロトコル</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="206"/> + <source>From this executable</source> + <translation type="unfinished">実行ファイル</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="843"/> + <source>Deny</source> + <translation type="unfinished">拒否</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="877"/> + <source>Allow</source> + <translation type="unfinished">許可</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="239"/> + <source>From this command line</source> + <translation type="unfinished">コマンドライン</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="169"/> + <source>From this user ID</source> + <translation type="unfinished">ユーザーID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="899"/> + <source>Name</source> + <translation type="unfinished">名前</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="22"/> + <source>Enable</source> + <translation type="unfinished">有効</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="706"/> + <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them. + +000-allow-localhost +001-deny-broadcast +...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="713"/> + <source>leave blank to autocreate</source> + <translation type="unfinished">空にすると自動生成されます</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="46"/> + <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one. + +You must name the rule in such manner that it'll be checked first, because they're checked in alphabetical order. For example: + +[x] Priority - 000-priority-rule +[ ] Priority - 001-less-priority-rule</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="54"/> + <source>Priority rule</source> + <translation type="unfinished">優先ルール</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="74"/> + <source><html><head/><body><p>By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.<br/></p><p>If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="77"/> + <source>Case-sensitive</source> + <translation type="unfinished">大文字/小文字を区別</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="163"/> + <source>Applications</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="216"/> + <source><html><head/><body><p>This field will only match the executable path. It is not modifiable by the user.<br/></p><p>You can use regular expressions to deny executions from /tmp for example:<br/></p><p>^/tmp/.*$</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="229"/> + <source><html><head/><body><p>This field will contain and match the command line that was executed by the user.<br/></p><p>If the user typed the command, only the command will appear:</p><p>telnet 1.2.3.4<br/></p><p>If the user typed the absolute or relative path to the command, that is what will appear:</p><p>/usr/bin/telnet 1.2.3.4</p><p>../../../usr/bin/telnet 1.2.3.4</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="246"/> + <source>From this PID</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="287"/> + <source>Network</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="536"/> + <source>List of domains/IPs</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="542"/> + <source>To this list of network ranges</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="549"/> + <source>To this list of IPs</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="574"/> + <source><html><head/><body><p>Select a directory with files containing list of IPs to block or allow:</p><p>1.2.3.4.5</p><p>1.2.3.4.6</p><p>.</p><p>etc.</p><p>One IP per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="608"/> + <source><html><head/><body><p>Select a directory with files containing list of network ranges to block or allow:</p><p>1.2.3.0/24</p><p>80.34.56.0/20</p><p>.</p><p>etc.<br/></p><p>One Network Range per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="636"/> + <source><html><head/><body><p>Select a directory with lists of domains to block or allow.</p><p>Put inside that directory files with any extension containing lists of domains.</p><p><br/>The format of each entry of a list is as follow (hosts format):</p><p>127.0.0.1 www.domain.com</p><p>or </p><p>0.0.0.0 www.domain.com</p><p>Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="651"/> + <source>To this list of domains +(regular expressions)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="677"/> + <source><html><head/><body><p>Select a directory with files containing regular expressions of domains to block or allow:</p><p>.*\.example\.com</p><p>You can also use a domain as is: &quot;example.com&quot; , and it'll match whatever.example.com, whatever.example.com.localdomain, etc.</p><p>One domain per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="857"/> + <source>Reject</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>StatsDialog</name> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="34"/> + <source>OpenSnitch Network Statistics</source> + <translation type="unfinished">OpenSnitchネットワークモニター</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="290"/> + <source>Save to CSV.</source> + <translation>CSVファイルに保存</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="300"/> + <source>Ctrl+S</source> + <translation type="unfinished">Ctrl+S</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="351"/> + <source>Create a new rule</source> + <translation type="unfinished">ルールを新規作成</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="381"/> + <source><html><head/><body><p><span style=" font-size:11pt; font-weight:600;">hostname - 192.168.1.1</span></p></body></html></source> + <translation type="unfinished"><html><head/><body><p><span style=" font-size:11pt; font-weight:600;">ホスト名 - 192.168.1.1</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="420"/> + <source>Status</source> + <translation type="unfinished">状態</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1665"/> + <source>-</source> + <translation type="unfinished">-</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="464"/> + <source>Start or Stop interception</source> + <translation type="unfinished">サービスを開始/停止</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="509"/> + <source>Events</source> + <translation type="unfinished">イベント</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="94"/> + <source>Filter</source> + <translation type="unfinished">絞り込み</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="107"/> + <source>Allow</source> + <translation type="unfinished">許可中</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="116"/> + <source>Deny</source> + <translation type="unfinished">拒否中</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="143"/> + <source>Ex.: firefox</source> + <translation type="unfinished">例:firefox</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="199"/> + <source>50</source> + <translation type="unfinished">50</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="204"/> + <source>100</source> + <translation type="unfinished">100</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="209"/> + <source>200</source> + <translation type="unfinished">200</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="214"/> + <source>300</source> + <translation type="unfinished">300</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="227"/> + <source>Delete all intercepted events</source> + <translation type="unfinished">記録した全てのイベント履歴を消去</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="748"/> + <source>Nodes</source> + <translation type="unfinished">ノード</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="554"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on the Addr column to view details of a node)</span></p></body></html></source> + <translation type="obsolete"><html><head/><body><p><span style=" font-size:7pt;">(項目をダブルクリックでノードの詳細を確認できます)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1569"/> + <source>Rules</source> + <translation type="unfinished">ルール</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="857"/> + <source>enable</source> + <translation type="unfinished">有効</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="864"/> + <source>Edit rule</source> + <translation type="unfinished">ルールを編集</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="878"/> + <source>Delete rule</source> + <translation type="unfinished">ルールを削除</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="674"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on the Name column to view details of a rule)</span></p></body></html></source> + <translation type="obsolete"><html><head/><body><p><span style=" font-size:7pt;">(項目をダブルクリックでルールの詳細を確認できます)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="692"/> + <source>search rule name</source> + <translation type="obsolete">ルール名を検索</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="704"/> + <source>Application rules</source> + <translation type="unfinished">アプリケーションのルール</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="806"/> + <source>Permanent</source> + <translation type="unfinished">永久ルール</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="815"/> + <source>Temporary</source> + <translation type="unfinished">一時ルール</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="933"/> + <source>Hosts</source> + <translation type="unfinished">ホスト</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1364"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click to view details of an item)</span></p></body></html></source> + <translation type="obsolete"><html><head/><body><p><span style=" font-size:7pt;">(項目をダブルクリックで詳細を確認できます)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="926"/> + <source>Delete all intercepted hosts</source> + <translation type="obsolete">記録した全てのホストを消去</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1020"/> + <source>Applications</source> + <translation type="unfinished">アプリケーション</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1051"/> + <source>Delete all intercepted applications</source> + <translation type="obsolete">記録した全てのアプリケーション履歴を消去</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1127"/> + <source>Addresses</source> + <translation type="unfinished">アドレス</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1159"/> + <source>Delete all intercepted addresses</source> + <translation type="obsolete">記録した全てのアドレスを消去</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1214"/> + <source>Ports</source> + <translation type="unfinished">ポート</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1261"/> + <source>Delete all intercepted ports</source> + <translation type="obsolete">記録した全てのポートを消去</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1298"/> + <source>Users</source> + <translation type="unfinished">ユーザー</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1371"/> + <source>Delete all intercepted users</source> + <translation type="obsolete">記録した全てのユーザー履歴を消去</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1404"/> + <source>Connections</source> + <translation type="unfinished">通過</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1459"/> + <source>Dropped</source> + <translation type="unfinished">ブロック</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1514"/> + <source>Uptime</source> + <translation type="unfinished">実行時間</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1639"/> + <source>Version</source> + <translation type="unfinished">バージョン</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="699"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on a row to view details of a rule)</span></p></body></html></source> + <translation type="obsolete"><html><head/><body><p><span style=" font-size:7pt;">(項目をダブルクリックするとルールの詳細が確認できます)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="912"/> + <source>Delete connections that matched this rule</source> + <translation type="unfinished">このルールにマッチした接続を削除します</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="797"/> + <source>All applications</source> + <translation type="unfinished">全てのアプリケーション</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="125"/> + <source>Reject</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="177"/> + <source>0</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>contextual_menu</name> + <message> + <location filename="../../../opensnitch/service.py" line="43"/> + <source>Statistics</source> + <translation type="unfinished">ダッシュボードを開く</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="46"/> + <source>Help</source> + <translation type="unfinished">ヘルプ</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="47"/> + <source>Close</source> + <translation type="unfinished">終了</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="44"/> + <source>Enable</source> + <translation type="unfinished">有効化</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="45"/> + <source>Disable</source> + <translation type="unfinished">無効化</translation> + </message> +</context> +<context> + <name>notifications</name> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="518"/> + <source>System notifications are not available, you need to install python3-notify2.</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>popups</name> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="52"/> + <source>until reboot</source> + <translation type="unfinished">再起動するまで</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="54"/> + <source>forever</source> + <translation type="unfinished">永久に</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="91"/> + <source>Allow</source> + <translation type="unfinished">許可</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="92"/> + <source>Deny</source> + <translation type="unfinished">拒否</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="285"/> + <source>Outgoing connection</source> + <translation type="unfinished">外部への接続</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="290"/> + <source>Process launched from:</source> + <translation type="unfinished">プロセスの実行元:</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="321"/> + <source>from this executable</source> + <translation type="unfinished">次の実行ファイルを</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="325"/> + <source>from this command line</source> + <translation type="unfinished">次のコマンドラインを</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="327"/> + <source>to port {0}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="390"/> + <source>to {0}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="330"/> + <source>from user {0}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="347"/> + <source>to {0}.*</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="400"/> + <source>to *.{0}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="434"/> + <source><b>Remote</b> process %s running on <b>%s</b></source> + <translation type="unfinished"><b>リモート</b>プロセス %s は <b>%s</b> で実行中です</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="438"/> + <source>is connecting to <b>%s</b> on %s port %d</source> + <translation type="unfinished">は <b>%s</b> の %s ポート %d 番に接続しています</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="444"/> + <source>is attempting to resolve <b>%s</b> via %s, %s port %d</source> + <translation type="unfinished">は<b>%s</b> を %sの %s ポート %dで解決しようとしています</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="334"/> + <source>from this PID</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/notifications.py" line="105"/> + <source>New outgoing connection</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>preferences</name> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="299"/> + <source>Exception saving config: {0}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="323"/> + <source>Warning</source> + <translation type="unfinished">警告</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="323"/> + <source>You must select a file for the database<br>or choose "In memory" type.</source> + <translation type="unfinished">データベースの保存ファイルを選択するか、方式「メモリ内」を選択する必要があります。</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="329"/> + <source>DB type changed</source> + <translation type="unfinished">データベース方式が変更されました</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="329"/> + <source>Restart the GUI in order effects to take effect</source> + <translation type="unfinished">GUIを再起動後反映されます</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="389"/> + <source>Applying configuration on {0} ...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="208"/> + <source>Server address can not be empty</source> + <translation type="unfinished">サーバーアドレスは空白にすることはできません</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="238"/> + <source>Error loading {0} configuration</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="448"/> + <source>Configuration applied.</source> + <translation type="unfinished">構成は反映されました。</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="450"/> + <source>Error applying configuration: {0}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="480"/> + <source>Hover the mouse over the texts to display the help<br><br>Don't forget to visit the wiki: <a href="{0}">{0}</a></source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>proc_details</name> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="97"/> + <source><b>Error loading process information:</b> <br><br> + +</source> + <translation type="unfinished"><b>プロセス情報の読み込みでエラーが発生しました:</b> <br><br> + +</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="116"/> + <source><b>Error stopping monitoring process:</b><br><br></source> + <translation type="unfinished"><b>プロセス監視の停止中にエラーが発生しました:</b><br><br></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="156"/> + <source>loading...</source> + <translation type="unfinished">読み込み中...</translation> + </message> +</context> +<context> + <name>rules</name> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="162"/> + <source>There're no nodes connected.</source> + <translation type="unfinished">接続しているノードがありません。</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="203"/> + <source>Rule applied.</source> + <translation type="unfinished">ルールが反映されました。</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="205"/> + <source>Error applying rule: {0}</source> + <translation type="unfinished">ルールの反映に失敗しました: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="527"/> + <source>protocol can not be empty, or uncheck it</source> + <translation type="unfinished">プロトコルを指定するかチェックを外してください</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="541"/> + <source>Protocol regexp error</source> + <translation type="unfinished">プロトコルの正規表現記法が誤っています</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="545"/> + <source>process path can not be empty</source> + <translation type="unfinished">プロセスのパスを指定してください</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="559"/> + <source>Process path regexp error</source> + <translation type="unfinished">プロセスパスの正規表現記法が誤っています</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="563"/> + <source>command line can not be empty</source> + <translation type="unfinished">コマンドラインを指定してください</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="577"/> + <source>Command line regexp error</source> + <translation type="unfinished">コマンドラインの正規表現記法が誤っています</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="581"/> + <source>Dest port can not be empty</source> + <translation type="unfinished">宛先ポートを指定してください</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="595"/> + <source>Dst port regexp error</source> + <translation type="unfinished">宛先ポートの正規表現記法が誤っています</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="599"/> + <source>Dest host can not be empty</source> + <translation type="unfinished">宛先ホストを指定してください</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="613"/> + <source>Dst host regexp error</source> + <translation type="unfinished">宛先ホストの正規表現記法が誤っています</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="617"/> + <source>Dest IP/Network can not be empty</source> + <translation type="unfinished">宛先IP/ネットワークを指定してください</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="639"/> + <source>Dst IP regexp error</source> + <translation type="unfinished">宛先IPの正規表現記法が誤っています</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="651"/> + <source>User ID can not be empty</source> + <translation type="unfinished">ユーザーIDを指定してください</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="665"/> + <source>User ID regexp error</source> + <translation type="unfinished">ユーザーIDの正規表現記法が誤っています</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="739"/> + <source>Lists field cannot be empty</source> + <translation type="unfinished">リスト項目を指定してください</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="741"/> + <source>Lists field must be a directory</source> + <translation type="unfinished">リスト項目には必ずディレクトリを指定してください</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="777"/> + <source><b>Rule not supported</b></source> + <translation type="unfinished"><b>ルールをサポートしていません</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="429"/> + <source><b>Error loading rule</b></source> + <translation type="unfinished"><b>ルールの読み込みに失敗しました</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="179"/> + <source>There's already a rule with this name.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="669"/> + <source>PID field can not be empty</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="683"/> + <source>PID field regexp error</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="764"/> + <source>Select at least one field.</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>stats</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <translation type="obsolete">名前</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="294"/> + <source>Address</source> + <translation type="obsolete">アドレス</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="176"/> + <source>Status</source> + <translation type="obsolete">状態</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="177"/> + <source>Hostname</source> + <translation type="obsolete">ホスト名</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="183"/> + <source>Version</source> + <translation type="obsolete">バージョン</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="180"/> + <source>Rules</source> + <translation type="obsolete">ルール</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Time</source> + <translation type="obsolete">時間</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Action</source> + <translation type="obsolete">アクション</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Duration</source> + <translation type="obsolete">有効期間</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Node</source> + <translation type="obsolete">ノード</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>Enabled</source> + <translation type="obsolete">有効</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="253"/> + <source>Hits</source> + <translation type="unfinished">回数</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>Protocol</source> + <translation type="obsolete">プロトコル</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="310"/> + <source>Not running</source> + <translation type="unfinished">停止中</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="311"/> + <source>Disabled</source> + <translation type="unfinished">無効</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="312"/> + <source>Running</source> + <translation type="unfinished">実行中</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="568"/> + <source>OpenSnitch Network Statistics {0}</source> + <translation type="unfinished">OpenSnitch ネットワークモニター {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="570"/> + <source>OpenSnitch Network Statistics for {0}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="731"/> + <source>Disable</source> + <translation type="unfinished">無効化</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="733"/> + <source>Enable</source> + <translation type="unfinished">有効化</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="736"/> + <source>Duplicate</source> + <translation type="unfinished">複製</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="737"/> + <source>Edit</source> + <translation type="unfinished">編集</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="738"/> + <source>Delete</source> + <translation type="unfinished">削除</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="761"/> + <source> Your are about to delete this rule. </source> + <translation type="unfinished"> このルールを消去しようとしています。 </translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1244"/> + <source> Are you sure?</source> + <translation type="unfinished"> 宜しいですか?</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="891"/> + <source>Rule not found by that name and node</source> + <translation type="unfinished">該当するルールが見つかりませんでした</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1837"/> + <source>Save as CSV</source> + <translation type="unfinished">CSVファイルに保存</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="921"/> + <source><b>Error:</b><br><br></source> + <comment>{0}</comment> + <translation type="unfinished"><b>エラーr:</b><br><br></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="928"/> + <source>Warning:</source> + <translation type="unfinished">警告:</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="717"/> + <source>Allow</source> + <translation type="unfinished">許可</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="718"/> + <source>Deny</source> + <translation type="unfinished">拒否</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="722"/> + <source>Always</source> + <translation type="unfinished">常に</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="723"/> + <source>Until reboot</source> + <translation type="unfinished">再起動するまで</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1244"/> + <source> You are about to delete this rule. </source> + <translation type="unfinished"> このルールを削除しようとしています。 </translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <comment>xxxxx</comment> + <translation type="obsolete">名前</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">名前</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="294"/> + <source>Address</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">アドレス</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="295"/> + <source>Status</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">状態</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="296"/> + <source>Hostname</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">ホスト名</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="297"/> + <source>Version</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">バージョン</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="298"/> + <source>Rules</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">ルール</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Time</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">時間</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Action</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">アクション</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Duration</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">有効期間</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Node</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">ノード</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>Enabled</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">有効</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="304"/> + <source>Hits</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">回数</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>Protocol</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">プロトコル</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="392"/> + <source>Name</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">名前</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="288"/> + <source>Address</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">アドレス</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="379"/> + <source>Status</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">状態</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="380"/> + <source>Hostname</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">ホスト名</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="386"/> + <source>Version</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">バージョン</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="383"/> + <source>Rules</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">ルール</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="390"/> + <source>Time</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">時間</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="395"/> + <source>Action</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">アクション</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="396"/> + <source>Duration</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">有効期間</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="391"/> + <source>Node</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">ノード</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="393"/> + <source>Enabled</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">有効</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="405"/> + <source>Hits</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">回数</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Protocol</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">プロトコル</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Process</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">プロセス</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Destination</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">宛先</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="306"/> + <source>Rule</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">ルール</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="307"/> + <source>UserID</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">ユーザーID</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="377"/> + <source>LastConnection</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">最終接続</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Args</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">引数</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>DstIP</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">宛先IP</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="304"/> + <source>DstHost</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">宛先ホスト</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>DstPort</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">宛先ポート</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="252"/> + <source>What</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="709"/> + <source>Apply to</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="719"/> + <source>Reject</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="25"/> + <source>Network name</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="378"/> + <source>Addr</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="382"/> + <source>Uptime</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="384"/> + <source>Connections</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="385"/> + <source>Dropped</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="404"/> + <source>What</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="394"/> + <source>Precedence</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="644"/> + <source>New node connected</source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> diff --git a/ui/i18n/locales/lt_LT/opensnitch-lt_LT.ts b/ui/i18n/locales/lt_LT/opensnitch-lt_LT.ts new file mode 100644 index 0000000..efc3363 --- /dev/null +++ b/ui/i18n/locales/lt_LT/opensnitch-lt_LT.ts @@ -0,0 +1,2408 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="lt"> +<context> + <name>Dialog</name> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="34"/> + <source>opensnitch-qt</source> + <translation>opensnitch-qt</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="299"/> + <source>User ID</source> + <translation>Vartotojo ID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="333"/> + <source><html><head/><body><p><span style=" font-weight:600;">Executed from</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-weight:600;">Ä®vykdyta iÅ¡</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="630"/> + <source>TextLabel</source> + <translation>TekstoEtiketė</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="426"/> + <source>Source IP</source> + <translation>Å altinio IP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="449"/> + <source>Process ID</source> + <translation>Proceso ID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="582"/> + <source>Destination IP</source> + <translation>Paskirties IP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="605"/> + <source>Dst Port</source> + <translation>Paskirties prievadas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="679"/> + <source>from this executable</source> + <translation>iÅ¡ Å¡io vykdomojo failo</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="684"/> + <source>from this command line</source> + <translation>iÅ¡ Å¡ios komandinės eilutės</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="689"/> + <source>this destination port</source> + <translation>Å¡is paskirties prievadas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="694"/> + <source>this user</source> + <translation>Å¡is vartotojas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="699"/> + <source>this destination ip</source> + <translation>Å¡is paskirties ip</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="728"/> + <source>once</source> + <translation>kartą</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="733"/> + <source>30s</source> + <translation>30 sek.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="738"/> + <source>5m</source> + <translation>5 min.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="743"/> + <source>15m</source> + <translation>15 min.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="748"/> + <source>30m</source> + <translation>30 min.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="753"/> + <source>1h</source> + <translation>1 val.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="706"/> + <source>for this session</source> + <translation type="obsolete">durante esta sesión</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="763"/> + <source>forever</source> + <translation>amžinai</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="789"/> + <source>Deny</source> + <translation>Drausti</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="818"/> + <source>Allow</source> + <translation>Leisti</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="847"/> + <source>+</source> + <translation>+</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="758"/> + <source>until reboot</source> + <translation>iki perkrovimo</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="704"/> + <source>from this PID</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>PreferencesDialog</name> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="14"/> + <source>Preferences</source> + <translation>Nuostatos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="472"/> + <source>UI</source> + <translation>Vartotojo sąsaja</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="54"/> + <source><html><head/><body><p>This timeout is the countdown you see when a pop-up dialog is shown.</p></body></html></source> + <translation type="obsolete">Este timeout es la cuenta atrás que aparece cuando se muestra una ventana emergente</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="454"/> + <source>Default timeout</source> + <translation>Numatytasis laiko limitas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="331"/> + <source>Pop-up default duration</source> + <translation>Iššokančiojo lango numatytoji trukmė</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="777"/> + <source>Default duration</source> + <translation>Numatytoji trukmė</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="162"/> + <source>Pop-up default action</source> + <translation type="obsolete">Acción por defecto de la ventana emergente</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="483"/> + <source>Default action</source> + <translation type="obsolete">Acción por defecto</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="314"/> + <source>Default target</source> + <translation>Numatytasis tikslas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="170"/> + <source>center</source> + <translation>centre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="175"/> + <source>top right</source> + <translation>virÅ¡uje deÅ¡inėje</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="180"/> + <source>bottom right</source> + <translation>apačioje deÅ¡inėje</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="185"/> + <source>top left</source> + <translation>virÅ¡uje kairėje</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="190"/> + <source>bottom left</source> + <translation>apačioje kairėje</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="167"/> + <source>Prompt dialog default position on screen</source> + <translation type="obsolete">Posición por defecto</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="274"/> + <source>by executable</source> + <translation>pagal vykdomąjį failą</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="279"/> + <source>by command line</source> + <translation>pagal komandinę eilutę</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="284"/> + <source>by destination port</source> + <translation>pagal paskirties prievadą</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="289"/> + <source>by destination ip</source> + <translation>pagal paskirties ip</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="294"/> + <source>by user id</source> + <translation>pagal vartotojo ID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="881"/> + <source>once</source> + <translation>kartą</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="210"/> + <source>30s</source> + <translation>30 sek.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="215"/> + <source>5m</source> + <translation>5 min.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="220"/> + <source>15m</source> + <translation>15 min.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="225"/> + <source>30m</source> + <translation>30 min.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="230"/> + <source>1h</source> + <translation>1 val.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="240"/> + <source>for this session</source> + <translation type="obsolete">durante esta sesión</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="240"/> + <source>forever</source> + <translation>amžinai</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="923"/> + <source>deny</source> + <translation>drausti</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="932"/> + <source>allow</source> + <translation>leisti</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="406"/> + <source>Disable pop-ups, only display an alert</source> + <translation type="obsolete">IÅ¡jungti iššokančius langus, rodyti tik įspėjimą</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="734"/> + <source>Nodes</source> + <translation>Mazgai</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="740"/> + <source>Process monitor method</source> + <translation>Proceso stebėjimo metodas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="774"/> + <source><html><head/><body><p>The default duration will take place when there's no UI connected.</p></body></html></source> + <translation><html><head/><body><p>Numatytoji trukmė bus taikoma, kai nėra prijungtos vartotojo sąsajos.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="899"/> + <source><html><head/><body><p>Address of the node.</p><p>Default: unix:///tmp/osui.sock (unix:// is mandatory if it's a Unix socket)</p><p>It can also be an IP address with the port: 127.0.0.1:50051</p></body></html></source> + <translation><html><head/><body><p>Mazgo adresas </p><p>Numatytoji reikÅ¡mė: unix:///tmp/osui.sock (unix:// privaloma, jei tai Unix lizdas) </p><p>Tai taip pat gali bÅ«ti IP adresas su prievadu: 127.0.0.1:50051</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="902"/> + <source>Address</source> + <translation>Adresas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1042"/> + <source>Default log level</source> + <translation>Numatytasis registravimo žurnale lygis</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="950"/> + <source>Version</source> + <translation>Versija</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="813"/> + <source><html><head/><body><p>The default action will take place when there's no UI connected.</p></body></html></source> + <translation><html><head/><body><p>Numatytasis veiksmas bus atliekamas, kai nėra prijungtos vartotojo sąsajos.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="757"/> + <source><html><head/><body><p>Log file to write logs.<br/></p><p>/dev/stdout will print logs to the standard output.</p></body></html></source> + <translation><html><head/><body><p>Žurnalo failas, į kurį įraÅ¡omi žurnalo įraÅ¡ai.<br/></p><p>/dev/stdout spausdins žurnalus į standartinę iÅ¡vestį.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="760"/> + <source>Log file</source> + <translation>Žurnalo failas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="578"/> + <source><html><head/><body><p>If checked, opensnitch will prompt you to allow or deny connections that don't have an asocciated PID, due to several reasons.</p><p>The pop-up dialog will only contain information about the network connection.</p></body></html></source> + <translation type="obsolete">Si marcas esta opción, OpenSnitch te preguntará para Aceptar o Denegar conexiones que no tengan un PID asociado por diferentes razones. + +La ventana emergente sólo contendrá información relativa a la conexión. + +Nota: Estas conexiones no tienen por qué indicar que algo sospechoso está sucediendo. Simplemente +es que no hemos descubierto el PID (por ejemplo conexiones que no se originan en la máquina, o paquetes en mal estado).</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="581"/> + <source>Intercept Unknown Connections</source> + <translation type="obsolete">Interceptar conexiones desconocidas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="832"/> + <source>HostName</source> + <translation>Kompiuterio vardas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1001"/> + <source>unix:///tmp/osui.sock</source> + <translation>unix:///tmp/osui.sock</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="886"/> + <source>until restart</source> + <translation>iki perkrovimo</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="891"/> + <source>always</source> + <translation>visada</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1013"/> + <source>/var/log/opensnitchd.log</source> + <translation>/var/log/opensnitchd.log</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1018"/> + <source>/dev/stdout</source> + <translation>/dev/stdout</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="790"/> + <source>Apply configuration to all nodes</source> + <translation>Taikyti konfigÅ«raciją visiems mazgams</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1057"/> + <source>Database</source> + <translation>Duomenų bazė</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1092"/> + <source>In memory</source> + <translation>Atmintyje</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1097"/> + <source>File</source> + <translation>Failas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1363"/> + <source>Close</source> + <translation>Uždaryti</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1374"/> + <source>Apply</source> + <translation>Taikyti</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1385"/> + <source>Save</source> + <translation>IÅ¡saugoti</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="235"/> + <source>until reboot</source> + <translation>iki perkrovimo</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1111"/> + <source>Database type</source> + <translation>Duomenų bazės tipas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1118"/> + <source>Select</source> + <translation>Pasirinkti</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="83"/> + <source>Pop-ups default options</source> + <translation type="obsolete">Opciones por defecto de las ventanas emergentes</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="367"/> + <source>Pop-ups default position on screen</source> + <translation type="obsolete">Posición en pantalla</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="102"/> + <source><html><head/><body><p>The advanced view allows you to apply more filters on a connection</p><p>when a pop-up appears.</p></body></html></source> + <translation type="obsolete">La vista avanzada permite filtrar conexiones por más parámetros</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="350"/> + <source>Show advanced view by default</source> + <translation>Rodyti iÅ¡plėstinį rodinį automatiÅ¡kai</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="660"/> + <source>Action</source> + <translation>Veiksmas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="366"/> + <source><html><head/><body><p>If checked, the pop-ups will be displayed with the advanced view active.</p></body></html></source> + <translation><html><head/><body><p>Jei pažymėta, iššokantys langai bus rodomi su aktyviu iÅ¡plėstiniu rodiniu.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="334"/> + <source>Duration</source> + <translation>Trukmė</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="254"/> + <source><html><head/><body><p>By default when a new pop-up appears, in its simplest form, you'll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).</p><p>With these options, you can choose multiple fields to filter connections for.</p></body></html></source> + <translation><html><head/><body><p>By default when a new pop-up appears, in its simplest form, you'll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).</p><p>With these options, you can choose multiple fields to filter connections for.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="257"/> + <source>Filter connections also by:</source> + <translation>Taip pat filtruoti prisijungimus pagal:</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="362"/> + <source>If checked, this field will be checked when a pop-up is displayed</source> + <translation type="obsolete">Si lo seleccionas, este campo se usará para filtrar las conexiones</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="81"/> + <source>User ID</source> + <translation>Vartotojo ID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="97"/> + <source>Destination port</source> + <translation>Paskirties prievadas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="113"/> + <source>Destination IP</source> + <translation>Paskirties IP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="451"/> + <source><html><head/><body><p>This timeout is the countdown you see when a pop-up dialog is shown.</p><p>If the pop-up is not answered, the default options will be applied.</p></body></html></source> + <translation><html><head/><body><p>Å is laiko limitas yra atgalinis laikmatis, kurį matote, kai rodomas iššokantis dialogo langas.</p><p>Jei į iššokantį dialogo langą neatsakoma, taikomos numatytosios parinktys.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="347"/> + <source>The advanced view allows you to easily select multiple fields to filter connections</source> + <translation>IÅ¡plėstinis vaizdas leidžia lengvai pasirinkti kelis laukus, kad bÅ«tų galima filtruoti prisijungimus</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="110"/> + <source>If checked, this field will be selected when a pop-up is displayed</source> + <translation>Jei pažymėta, Å¡is laukas bus pasirinktas, kai bus rodomas iššokantis langas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="150"/> + <source><html><head/><body><p>Pop-up default action.</p><p>When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.</p><p><br/></p><p>While a pop-up is asking the user to allow or deny a connection:</p><p>1. new outgoing connections are denied.</p><p>2. known connections are allowed or denied based on the rules defined by the user.</p></body></html></source> + <translation><html><head/><body><p>Iššokančio lango numatytasis veiksmas.</p><p>Kai ruoÅ¡iamasi užmegzti naują iÅ¡einantį ryšį, Å¡is veiksmas bus pasirinktas pagal numatytuosius nustatymus, todėl, jei suveiks laiko limitas, bus taikoma Å¡i parinktis.</p><p><br/></p><p>Kai iššokančiame lange praÅ¡oma leisti arba neleisti prisijungti: </p><p>1. Nauji iÅ¡einantys ryÅ¡iai neleidžiami.</p><p>2. Žinomi ryÅ¡iai leidžiami arba neleidžiami pagal vartotojo nustatytas taisykles.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="816"/> + <source>Default action when the GUI is disconnected</source> + <translation>Default action when the GUI is disconnected</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="912"/> + <source>Debug invalid connections</source> + <translation>Debug invalid connections</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="39"/> + <source>Pop-ups</source> + <translation>Iššokantys langai</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="64"/> + <source>Default options</source> + <translation>Numatytosios parinktys</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="321"/> + <source>Default position on screen</source> + <translation>Numatytoji padėtis ekrane</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="713"/> + <source>any temporary rules</source> + <translation>bet kokios laikinos taisyklės</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="478"/> + <source><html><head/><body><p>When this option is selected, the rules of the selected duration won't be added to the list of temporary rules in the GUI.</p><p><br/></p><p>Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</p></body></html></source> + <translation><html><head/><body><p>When this option is selected, the rules of the selected duration won't be added to the list of temporary rules in the GUI.</p><p><br/></p><p>Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="481"/> + <source>Don't save rules of duration</source> + <translation>Don't save rules of duration</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="463"/> + <source>Show events columns</source> + <translation type="obsolete">Mostrar columnas de la pestaña Eventos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="596"/> + <source>Time</source> + <translation>Laikas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="676"/> + <source>Destination</source> + <translation>Paskirties vieta</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="644"/> + <source>Protocol</source> + <translation>Protokolas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="692"/> + <source>Process</source> + <translation>Procesas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="612"/> + <source>Rule</source> + <translation>Taisyklė</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="628"/> + <source>Node</source> + <translation>Mazgas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="584"/> + <source>Events tab columns</source> + <translation>Events tab columns</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="299"/> + <source>by PID</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="461"/> + <source>Disable pop-ups, only display an notification</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="494"/> + <source>Desktop notifications</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="512"/> + <source>Use system notifications</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="528"/> + <source>Use Qt notifications</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="557"/> + <source>Test</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="570"/> + <source>System</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="705"/> + <source>Theme</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="909"/> + <source><html><head/><body><p>If checked, OpenSnitch will prompt you to allow or deny connections that don't have an associated PID, due to several reasons, mostly due to bad state connections.</p><p>The pop-up dialog will only contain information about the network connection.</p><p>There're some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1196"/> + <source>minutes</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1222"/> + <source>Minutes between events purges</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1245"/> + <source>days</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1255"/> + <source>Maximum days of events to keep</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>ProcessDetailsDialog</name> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="14"/> + <source>Process details</source> + <translation>Proceso informacija</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="61"/> + <source>loading...</source> + <translation>įkeliama…</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="81"/> + <source>CWD: loading...</source> + <translation>CWD: loading...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="93"/> + <source>mem stats: loading...</source> + <translation>mem stats: loading...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="121"/> + <source>Status</source> + <translation>BÅ«sena</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="135"/> + <source>Open files</source> + <translation>Atidaryti failus</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="149"/> + <source>I/O Statistics</source> + <translation>I/O Statistics</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="163"/> + <source>Memory mapped files</source> + <translation>Memory mapped files</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="177"/> + <source>Stack</source> + <translation>Stack</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="191"/> + <source>Environment variables</source> + <translation>Aplinkos kintamieji</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="210"/> + <source>Application pids</source> + <translation>Application pids</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="240"/> + <source>Start or stop monitoring this process</source> + <translation>Pradėti arba sustabdyti Å¡io proceso stebėjimą</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="256"/> + <source>Close</source> + <translation>Uždaryti</translation> + </message> +</context> +<context> + <name>RulesDialog</name> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="20"/> + <source>Rule</source> + <translation>Taisyklė</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="852"/> + <source>Node</source> + <translation>Mazgas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="875"/> + <source>Apply rule to all nodes</source> + <translation>Taikyti taisyklę visiems mazgams</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="254"/> + <source>From this command line</source> + <translation>IÅ¡ Å¡ios komandinės eilutės</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="341"/> + <source>From this executable</source> + <translation>IÅ¡ Å¡io vykdomojo failo</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="56"/> + <source>Action</source> + <translation>Veiksmas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="456"/> + <source>To this IP / Network</source> + <translation>Ä® šį IP / tinklą</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="97"/> + <source>once</source> + <translation>kartą</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="102"/> + <source>30s</source> + <translation>30 sek.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="107"/> + <source>5m</source> + <translation>5 min.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="112"/> + <source>15m</source> + <translation>15 min.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="117"/> + <source>30m</source> + <translation>30 min.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="122"/> + <source>1h</source> + <translation>1 val.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="230"/> + <source>until restart</source> + <translation type="obsolete">hasta reiniciar (el servicio)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="132"/> + <source>always</source> + <translation>visada</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="366"/> + <source>To this port</source> + <translation>Ä® šį prievadą</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="247"/> + <source>From this user ID</source> + <translation>IÅ¡ Å¡io vartotojo ID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="492"/> + <source>Commas or spaces are not allowed to specify multiple domains. + +Use regular expressions instead: +.*(opensnitch|duckduckgo).com +.*\.google.com + +or a single domain: +www.gnu.org - it'll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ... +gnu.org - it'll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source> + <translation>Kelių domenų nurodyti kableliais ar tarpais neleidžiama. + +Vietoj jų naudokite reguliariąsias iÅ¡raiÅ¡kas: +.*(opensnitch|duckduckgo).com +.*\.google.com + +arba vieną domeną: +www.gnu.org - atitiks tik www.gnu.org, nei ftp.gnu.org, nei www2.gnu.org, ... +gnu.org - atitiks tik gnu.org, www.gnu.org, ftp.gnu.org, ...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="503"/> + <source>www.domain.org, .*\.domain.org</source> + <translation>www.domenas.org, .*\.domenas.org</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="396"/> + <source><html><head/><body><p>Only TCP, UDP or UDPLITE are allowed</p><p>You can use regexp, i.e.: ^(TCP|UDP)$</p></body></html></source> + <translation><html><head/><body><p>Leidžiama naudoti tik TCP, UDP arba UDPLITE</p><p>Galite naudoti regexp, t. y.: ^(TCP|UDP)$</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="406"/> + <source>TCP</source> + <translation>TCP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="411"/> + <source>UDP</source> + <translation>UDP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="416"/> + <source>UDPLITE</source> + <translation>UDPLITE</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="421"/> + <source>TCP6</source> + <translation>TCP6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="426"/> + <source>UDP6</source> + <translation>UDP6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="431"/> + <source>UDPLITE6</source> + <translation>UDPLITE6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="513"/> + <source>You can specify a single IP: +- 192.168.1.1 + +or a regular expression: +- 192\.168\.1\.[0-9]+ + +multiple IPs: +- ^(192\.168\.1\.1|172\.16\.0\.1)$ + +You can also specify a subnet: +- 192.168.1.0/24 + +Note: Commas or spaces are not allowed to separate IPs or networks.</source> + <translation>Galite nurodyti vieną IP adresą: +- 192.168.1.1 + +arba reguliarią iÅ¡raiÅ¡ką: +- 192\.168\.1\.[0-9]+ + +kelių IP adresų: +- ^(192\.168\.1\.1|172\.16\.0\.1)$ + +Taip pat galite nurodyti potinklį: +- 192.168.1.0/24 + +Pastaba: kableliais ar tarpais atskirti IP ar tinklų negalima.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="532"/> + <source>LAN</source> + <translation>LAN</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="537"/> + <source>127.0.0.0/8</source> + <translation>127.0.0.0/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="542"/> + <source>192.168.0.0/24</source> + <translation>192.168.0.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="547"/> + <source>192.168.1.0/24</source> + <translation>192.168.1.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="552"/> + <source>192.168.2.0/24</source> + <translation>192.168.2.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="557"/> + <source>192.168.0.0/16</source> + <translation>192.168.0.0/16</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="562"/> + <source>169.254.0.0/16</source> + <translation>169.254.0.0/16</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="567"/> + <source>172.16.0.0/12</source> + <translation>172.16.0.0/12</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="572"/> + <source>10.0.0.0/8</source> + <translation>10.0.0.0/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="577"/> + <source>::1/128</source> + <translation>::1/128</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="582"/> + <source>fc00::/7</source> + <translation>fc00::/7</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="587"/> + <source>ff00::/8</source> + <translation>ff00::/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="592"/> + <source>fe80::/10</source> + <translation>fe80::/10</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="597"/> + <source>fd00::/8</source> + <translation>fd00::/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="89"/> + <source>Duration</source> + <translation>Trukmė</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="449"/> + <source>Protocol</source> + <translation>Protokolas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="373"/> + <source>To this host</source> + <translation>To this host</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="151"/> + <source>Deny</source> + <translation>Drausti</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="191"/> + <source>Allow</source> + <translation>Leisti</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="912"/> + <source>Name</source> + <translation>Pavadinimas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="884"/> + <source>Enable</source> + <translation>Ä®jungti</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="928"/> + <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them. + +000-allow-localhost +001-deny-broadcast +...</source> + <translation>Taisyklės tikrinamos abėcėlės tvarka, todėl galite jas atitinkamai pavadinę nustatyti jų prioritetus. + +000-allow-localhost +001-deny-broadcast +...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="935"/> + <source>leave blank to autocreate</source> + <translation>palikite tuščią, kad sukurtumėte automatiÅ¡kai</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="891"/> + <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one. + +You must name the rule in such manner that it'll be checked first, because they're checked in alphabetical order. For example: + +[x] Priority - 000-priority-rule +[ ] Priority - 001-less-priority-rule</source> + <translation>Jei pažymėta, Å¡i taisyklė bus virÅ¡esnė už kitas taisykles. Po Å¡ios taisyklės nebus tikrinamos jokios kitos taisyklės. + +Taisyklę turite pavadinti taip, kad ji bÅ«tų tikrinama pirma, nes taisyklės tikrinamos abėcėlės tvarka. Pavyzdžiui: + +[x] Prioritetas - 000-prioritetinė-taisyklė +[ ] Prioritetas - 001-mažesnio prioriteto taisyklė</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="899"/> + <source>Priority rule</source> + <translation>Prioritetinė taisyklė</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="770"/> + <source><html><head/><body><p>By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.<br/></p><p>If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.</p></body></html></source> + <translation><html><head/><body><p>Pagal numatytuosius nustatymus taisyklių lauke neribojamos didžiosios raidės, t. y., jei procesas bando prisijungti prie gOOgle.CoM, o jÅ«s turite taisyklę Deny .*google.com, prisijungimas bus užblokuotas.<br/></p><p>Jei pažymėsite šį laukelį, turėsite nurodyti tikslų (domeną, vykdomąjį failą, komandinę eilutę), kurią norite filtruoti.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="773"/> + <source>Case-sensitive</source> + <translation>Case-sensitive</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="442"/> + <source><html><head/><body><p>You can specify multiple ports using regular expressions:</p><p><br/></p><p>- 53, 80 or 443:</p><p>^(53|80|443)$</p><p><br/></p><p>- 53, 443 or 5551, 5552, 5553, etc:</p><p>^(53|443|555[0-9])$</p></body></html></source> + <translation><html><head/><body><p>You can specify multiple ports using regular expressions:</p><p><br/></p><p>- 53, 80 or 443:</p><p>^(53|80|443)$</p><p><br/></p><p>- 53, 443 or 5551, 5552, 5553, etc:</p><p>^(53|443|555[0-9])$</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="127"/> + <source>until reboot</source> + <translation>iki perkrovimo</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="658"/> + <source>To this list of domains</source> + <translation>Ä® šį domenų sąrašą</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="539"/> + <source><html><head/><body><p>Select a directory with lists of domains to block or allow.</p><p>Put inside that directory files with any extension containing lists of domains.</p><p><br/>The format of each entry of a list is as follow (hosts format):</p><p>127.0.0.1 www.domain.com</p><p>or </p><p>0.0.0.0 www.domain.com</p></body></html></source> + <translation type="obsolete"><html><head/><body><p>Pasirinkite katalogą su blokuojamų arba leidžiamų domenų sąraÅ¡ais.</p><p>Ä® tą katalogą įdėkite failus su bet kokiu plėtiniu, kuriuose yra domenų sąraÅ¡ai.</p><p><br/>Kiekvieno sąraÅ¡o įraÅ¡o formatas yra toks (pagrindinio kompiuterio formatas): </p><p>127.0.0.0.1 www.domain.com</p><p>arba </p><p>0.0.0.0.0 www.domenas.com</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="148"/> + <source>Deny will just discard the connection</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="165"/> + <source>Reject will drop the connection, and kill the socket that initiated it</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="168"/> + <source>Reject</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="185"/> + <source>Allow will allow the connection</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="221"/> + <source>Applications</source> + <translation type="unfinished">Programos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="230"/> + <source><html><head/><body><p>The value of this field is always the absolute path to the executable: /path/to/binary<br/></p><p>Examples:</p><p>- Simple: /path/to/binary</p><p>- Multiple paths: ^/usr/lib(64|)/firefox/firefox$</p><p>- Multiple binaries: ^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ </p><p>- Deny/Allow executions from /tmp:</p><p>^/(var/|)tmp/.*$<br/></p><p>For more examples visit the <a href="https://github.com/evilsocket/opensnitch/wiki/Rules-examples">wiki page</a> or ask on the <a href="https://github.com/evilsocket/opensnitch/discussions">Discussion forums</a>.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="240"/> + <source>Is regular expression</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="264"/> + <source><html><head/><body><p>This field will contain and match the command line that was executed by the user.<br/></p><p>If the user typed the command, only the command will appear:</p><p>telnet 1.2.3.4<br/></p><p>If the user typed the absolute or relative path to the command, that is what will appear:</p><p>/usr/bin/telnet 1.2.3.4</p><p>../../../usr/bin/telnet 1.2.3.4</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="274"/> + <source>From this PID</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="348"/> + <source>is regular expression</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="360"/> + <source>Network</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="610"/> + <source>List of domains/IPs</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="616"/> + <source>To this list of network ranges</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="623"/> + <source>To this list of IPs</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="649"/> + <source><html><head/><body><p>Select a directory with files containing list of IPs to block or allow:</p><p>1.2.3.4.5</p><p>1.2.3.4.6</p><p>.</p><p>etc.</p><p>One IP per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="684"/> + <source><html><head/><body><p>Select a directory with files containing list of network ranges to block or allow:</p><p>1.2.3.0/24</p><p>80.34.56.0/20</p><p>.</p><p>etc.<br/></p><p>One Network Range per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="712"/> + <source><html><head/><body><p>Select a directory with lists of domains to block or allow.</p><p>Put inside that directory files with any extension containing lists of domains.</p><p><br/>The format of each entry of a list is as follow (hosts format):</p><p>127.0.0.1 www.domain.com</p><p>or </p><p>0.0.0.0 www.domain.com</p><p>Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="727"/> + <source>To this list of domains +(regular expressions)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="754"/> + <source><html><head/><body><p>Select a directory with files containing regular expressions of domains to block or allow:</p><p>.*\.example\.com</p><p>You can also use a domain as is: &quot;example.com&quot; , and it'll match whatever.example.com, whatever.example.com.localdomain, etc.</p><p>One domain per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="764"/> + <source>More</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>StatsDialog</name> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="34"/> + <source>OpenSnitch Network Statistics</source> + <translation>OpenSnitch tinklo statistika</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="284"/> + <source>Save to CSV.</source> + <translation>Ä®raÅ¡yti į CSV.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="294"/> + <source>Ctrl+S</source> + <translation>Ctrl+S</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="330"/> + <source>Create a new rule</source> + <translation>Sukurti naują taisyklę</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="360"/> + <source><html><head/><body><p><span style=" font-size:11pt; font-weight:600;">hostname - 192.168.1.1</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-size:11pt; font-weight:600;">pagrindinio kompiuterio vardas - 192.168.1.1</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="399"/> + <source>Status</source> + <translation>Statusas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1627"/> + <source>-</source> + <translation>-</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="437"/> + <source>Start or Stop interception</source> + <translation>Pradėti arba sustabdyti perėmimą</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="482"/> + <source>Events</source> + <translation>Ä®vykiai</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="94"/> + <source>Filter</source> + <translation>Filtras</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="107"/> + <source>Allow</source> + <translation>Leisti</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="116"/> + <source>Deny</source> + <translation type="unfinished">Atmesti</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="143"/> + <source>Ex.: firefox</source> + <translation>Pvz.: firefox</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="199"/> + <source>50</source> + <translation>50</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="204"/> + <source>100</source> + <translation>100</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="209"/> + <source>200</source> + <translation>200</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="214"/> + <source>300</source> + <translation>300</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="724"/> + <source>Nodes</source> + <translation>Mazgai</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1531"/> + <source>Rules</source> + <translation>Taisyklės</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="833"/> + <source>enable</source> + <translation>įjungti</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="684"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on the Name column to view details of a rule)</span></p></body></html></source> + <translation type="obsolete">(doble click en la columna Nombre para ver los detalles)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="680"/> + <source>Application rules</source> + <translation>Application rules</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="782"/> + <source>Permanent</source> + <translation>Nuolatinė</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="791"/> + <source>Temporary</source> + <translation>Laikina</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="895"/> + <source>Hosts</source> + <translation>Hosts</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1364"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click to view details of an item)</span></p></body></html></source> + <translation type="obsolete"><html><head/><body><p><span style=" font-size:7pt;">(dukart spustelėkite, kad peržiÅ«rėtumėte detalią informaciją apie elementą)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="982"/> + <source>Applications</source> + <translation>Programos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1089"/> + <source>Addresses</source> + <translation>Adresai</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1176"/> + <source>Ports</source> + <translation>Prievadai</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1260"/> + <source>Users</source> + <translation>Vartotojai</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1366"/> + <source>Connections</source> + <translation>Connections</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1421"/> + <source>Dropped</source> + <translation>Dropped</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1476"/> + <source>Uptime</source> + <translation>Veikimo laikas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1601"/> + <source>Version</source> + <translation>Versija</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="227"/> + <source>Delete all intercepted events</source> + <translation type="unfinished">IÅ¡trinti visus perimtus įvykius</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="840"/> + <source>Edit rule</source> + <translation>Redaguoti taisyklę</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="854"/> + <source>Delete rule</source> + <translation>IÅ¡trinti taisyklę</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="699"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on a row to view details of a rule)</span></p></body></html></source> + <translation type="obsolete"><html><head/><body><p><span style=" font-size:7pt;">(dukart spustelėkite, kad peržiÅ«rėtumėte detalią informaciją apie taisyklę)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="773"/> + <source>All applications</source> + <translation>Visos programos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="125"/> + <source>Reject</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="177"/> + <source>0</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>contextual_menu</name> + <message> + <location filename="../../../opensnitch/service.py" line="43"/> + <source>Statistics</source> + <translation>Statistika</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="46"/> + <source>Help</source> + <translation>Pagalba</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="47"/> + <source>Close</source> + <translation>Uždaryti</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="44"/> + <source>Enable</source> + <translation>Ä®jungti</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="45"/> + <source>Disable</source> + <translation>IÅ¡jungti</translation> + </message> +</context> +<context> + <name>menu_close</name> + <message> + <location filename="../../../opensnitch/service.py" line="131"/> + <source>Close</source> + <translation type="obsolete">Cerrar</translation> + </message> +</context> +<context> + <name>menu_help</name> + <message> + <location filename="../../../opensnitch/service.py" line="126"/> + <source>Help</source> + <translation type="obsolete">Ayuda</translation> + </message> +</context> +<context> + <name>menu_statistics</name> + <message> + <location filename="../../../opensnitch/service.py" line="120"/> + <source>Statistics</source> + <translation type="obsolete">Eventos</translation> + </message> +</context> +<context> + <name>notifications</name> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="547"/> + <source>System notifications are not available, you need to install python3-notify2.</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>popups</name> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="91"/> + <source>Allow</source> + <translation>Leisti</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="92"/> + <source>Deny</source> + <translation>Drausti</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="54"/> + <source>forever</source> + <translation>amžinai</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="285"/> + <source>Outgoing connection</source> + <translation>Outgoing connection</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="290"/> + <source>Process launched from:</source> + <translation>Procesas paleistas iÅ¡:</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="325"/> + <source>from this command line</source> + <translation>iÅ¡ Å¡ios komandinės eilutės</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="321"/> + <source>from this executable</source> + <translation>iÅ¡ Å¡io vykdomojo failo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="208"/> + <source>Unknown process</source> + <translation type="obsolete">Proceso no encontrado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="52"/> + <source>until reboot</source> + <translation>iki perkrovimo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="327"/> + <source>to port {0}</source> + <translation>į prievadą {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="222"/> + <source><b>%s</b> is connecting to <b>%s</b> on %s port %d</source> + <translation type="obsolete"><b>%s</b> está conectándose a <b>%s</b> en el puerto %s %d</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="228"/> + <source><b>Remote</b> process <b>%s</b> running on <b>%s</b> is connecting to <b>%s</b> on %s port %d</source> + <translation type="obsolete">El proceso <b>remoto %s</b> ejecutándose en <b>%s</b> está conectándose a <b>%s</b> en el puerto %s %d</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="390"/> + <source>to {0}</source> + <translation>į {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="330"/> + <source>from user {0}</source> + <translation>iÅ¡ vartotojo {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="347"/> + <source>to {0}.*</source> + <translation>į {0}.*</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="400"/> + <source>to *.{0}</source> + <translation>į *.{0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="378"/> + <source>to *{0}</source> + <translation type="obsolete">į *{0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="434"/> + <source><b>Remote</b> process %s running on <b>%s</b></source> + <translation><b>Nuotolinis</b> procesas %s veikia <b>%s</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="438"/> + <source>is connecting to <b>%s</b> on %s port %d</source> + <translation>jungiasi prie <b>%s</b> per %s prievadą %d</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="444"/> + <source>is attempting to resolve <b>%s</b> via %s, %s port %d</source> + <translation>bando iÅ¡spręsti <b>%s</b> per %s, %s prievadą %d</translation> + </message> + <message> + <location filename="../../../opensnitch/notifications.py" line="108"/> + <source>New outgoing connection</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="334"/> + <source>from this PID</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>popups2</name> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="254"/> + <source><b>Remote</b> process <b>%s</b> running on <b>%s</b> is connecting to <b>%s</b> on %s port %d</source> + <translation type="obsolete">El proceso <b>remoto %s</b> ejecutándose en <b>%s</b> está conectándose a <b>%s</b> en el puerto %s %d</translation> + </message> +</context> +<context> + <name>preferences</name> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="171"/> + <source>Exception saving config: %s</source> + <translation type="obsolete">Error al guarda la configuración: %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="177"/> + <source>Applying configuration on %s ...</source> + <translation type="obsolete">Aplicando configuración en %s ...</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="230"/> + <source>Server address can not be empty</source> + <translation>Serverio adresas negali bÅ«ti tuščias</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="227"/> + <source>Error loading %s configuration</source> + <translation type="obsolete">Error al cargar la configuración %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="477"/> + <source>Configuration applied.</source> + <translation>KonfigÅ«racija pritaikyta.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="257"/> + <source>Error applying configuration: %s</source> + <translation type="obsolete">Error al aplicar la configuración: %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="321"/> + <source>Exception saving config: {0}</source> + <translation>IÅ¡imtis iÅ¡saugant konfigÅ«raciją: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="418"/> + <source>Applying configuration on {0} ...</source> + <translation>Taikoma konfigÅ«racija {0} ...</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="260"/> + <source>Error loading {0} configuration</source> + <translation>Ä®keliant {0} konfigÅ«raciją įvyko klaida</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="479"/> + <source>Error applying configuration: {0}</source> + <translation>Taikant konfigÅ«raciją įvyko klaida: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="345"/> + <source>Warning</source> + <translation>Ä®spėjimas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="345"/> + <source>You must select a file for the database<br>or choose "In memory" type.</source> + <translation>Turite pasirinkti duomenų bazės failą<br>arba pasirinkite tipą „Atmintyje“.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="351"/> + <source>DB type changed</source> + <translation>DB tipas pakeistas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="351"/> + <source>Restart the GUI in order effects to take effect</source> + <translation>Restart the GUI in order effects to take effect</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="509"/> + <source>Hover the mouse over the texts to display the help<br><br>Don't forget to visit the wiki: <a href="{0}">{0}</a></source> + <translation>Užveskite pelės žymeklį virÅ¡ teksto, kad bÅ«tų rodoma pagalba<br><br>NepamirÅ¡kite apsilankyti wiki: <a href="{0}">{0}</a></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="127"/> + <source>System</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="135"/> + <source>Themes not available. Install qt-material: pip3 install qt-material</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="387"/> + <source>UI theme changed</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="387"/> + <source>Restart the GUI in order to apply the new theme</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>proc_details</name> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="97"/> + <source><b>Error loading process information:</b> <br><br> + +</source> + <translation><b>Klaida įkeliant proceso informaciją:</b><br><br> + +</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="116"/> + <source><b>Error stopping monitoring process:</b><br><br></source> + <translation><b>Sustabdant stebėjimo procesą įvyko klaida:</b><br><br></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="156"/> + <source>loading...</source> + <translation>įkeliama…</translation> + </message> +</context> +<context> + <name>rules</name> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="164"/> + <source>There're no nodes connected.</source> + <translation>Nėra prijungtų mazgų.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="205"/> + <source>Rule applied.</source> + <translation>Taisyklė pritaikyta.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="123"/> + <source>Error applying rule: %s</source> + <translation type="obsolete">Error al aplicar la regla: %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="537"/> + <source>protocol can not be empty, or uncheck it</source> + <translation>protokolas negali bÅ«ti tuščias arba panaikinkite jo žymėjimą</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="551"/> + <source>Protocol regexp error</source> + <translation type="unfinished">Protokolo regexp klaida</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="555"/> + <source>process path can not be empty</source> + <translation>proceso kelias negali bÅ«ti tuščias</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="569"/> + <source>Process path regexp error</source> + <translation>Process path regexp error</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="573"/> + <source>command line can not be empty</source> + <translation>komandinė eilutė negali bÅ«ti tuščia</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="587"/> + <source>Command line regexp error</source> + <translation>Command line regexp error</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="591"/> + <source>Dest port can not be empty</source> + <translation>Paskirties prievadas negali bÅ«ti tuščias</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="605"/> + <source>Dst port regexp error</source> + <translation>Dst port regexp error</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="609"/> + <source>Dest host can not be empty</source> + <translation>Dest host can not be empty</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="623"/> + <source>Dst host regexp error</source> + <translation>Dst host regexp error</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="627"/> + <source>Dest IP/Network can not be empty</source> + <translation>Dest IP/Network can not be empty</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="649"/> + <source>Dst IP regexp error</source> + <translation>Dst IP regexp error</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="661"/> + <source>User ID can not be empty</source> + <translation>Vartotojo ID negali bÅ«ti tuščias</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="675"/> + <source>User ID regexp error</source> + <translation>User ID regexp error</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="207"/> + <source>Error applying rule: {0}</source> + <translation>Klaida taikant taisyklę: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="749"/> + <source>Lists field cannot be empty</source> + <translation>Sąrašų laukas negali bÅ«ti tuščias</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="751"/> + <source>Lists field must be a directory</source> + <translation>Lists field must be a directory</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="794"/> + <source><b>Rule not supported</b></source> + <translation><b>Taisyklė nepalaikoma</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="439"/> + <source><b>Error loading rule</b></source> + <translation><b>Klaida įkeliant taisyklę</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="181"/> + <source>There's already a rule with this name.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="679"/> + <source>PID field can not be empty</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="693"/> + <source>PID field regexp error</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="781"/> + <source>Select at least one field.</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>stats</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="310"/> + <source>Not running</source> + <translation>Neveikia</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="311"/> + <source>Disabled</source> + <translation>IÅ¡jungta</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="312"/> + <source>Running</source> + <translation>Veikia</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="412"/> + <source>OpenSnitch Network Statistics</source> + <translation type="obsolete">Eventos de OpenSnitch</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="414"/> + <source>OpenSnitch Network Statistics for</source> + <translation type="obsolete">Eventos de OpenSnitch de</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="899"/> + <source> Your are about to delete this rule. </source> + <translation> Ketinate iÅ¡trinti Å¡ią taisyklę. </translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1286"/> + <source> Are you sure?</source> + <translation> Ar esate tuo tikras?</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="583"/> + <source>OpenSnitch Network Statistics {0}</source> + <translation>OpenSnitch tinklo statistika {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="585"/> + <source>OpenSnitch Network Statistics for {0}</source> + <translation>{0} OpenSnitch tinklo statistika</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <translation type="obsolete">Nombre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="294"/> + <source>Address</source> + <translation type="obsolete">Dirección</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="295"/> + <source>Status</source> + <translation type="obsolete">Estado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="296"/> + <source>Hostname</source> + <translation type="obsolete">Hostname</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="297"/> + <source>Version</source> + <translation type="obsolete">Versión</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="298"/> + <source>Rules</source> + <translation type="obsolete">Reglas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Time</source> + <translation type="obsolete">Hora</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Action</source> + <translation type="obsolete">Acción</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Duration</source> + <translation type="obsolete">Duración</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Node</source> + <translation type="obsolete">Nodo</translation> + </message> + <message> + <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="24"/> + <source>Hits</source> + <translation type="unfinished">Total</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>Protocol</source> + <translation type="obsolete">Protocolo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1901"/> + <source>Save as CSV</source> + <translation>IÅ¡saugoti kaip CSV</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>Enabled</source> + <translation type="obsolete">Habilitado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="765"/> + <source>Delete</source> + <translation>IÅ¡rinti</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="948"/> + <source>always</source> + <translation type="obsolete">siempre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="580"/> + <source><b>Error:</b><br><br>{0}</source> + <translation type="obsolete"><b>Error:</b><br><br>{0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="758"/> + <source>Disable</source> + <translation>IÅ¡jungti</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="760"/> + <source>Enable</source> + <translation>Ä®jungti</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="763"/> + <source>Duplicate</source> + <translation>Duplicate</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="764"/> + <source>Edit</source> + <translation>Redaguoti</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="918"/> + <source>Rule not found by that name and node</source> + <translation>Taisyklė pagal šį pavadinimą ir mazgą nerasta</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="948"/> + <source><b>Error:</b><br><br></source> + <comment>{0}</comment> + <translation><b>Klaida:</b><br><br></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="955"/> + <source>Warning:</source> + <translation>Ä®spėjimas:</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="744"/> + <source>Allow</source> + <translation>Leisti</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="745"/> + <source>Deny</source> + <translation>Drausti</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="749"/> + <source>Always</source> + <translation>Visada</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="750"/> + <source>Until reboot</source> + <translation>Iki perkrovimo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1286"/> + <source> You are about to delete this rule. </source> + <translation> Ketinate iÅ¡trinti Å¡ią taisyklę. </translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="306"/> + <source>Process</source> + <translation type="obsolete">Aplicación</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="307"/> + <source>Destination</source> + <translation type="obsolete">Destino</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="308"/> + <source>Rule</source> + <translation type="obsolete">Regla</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="309"/> + <source>UserID</source> + <translation type="obsolete">UserID</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="310"/> + <source>LastConnection</source> + <translation type="obsolete">Última Conexión</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <comment>xxxxx</comment> + <translation type="obsolete">Nombre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Nombre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="294"/> + <source>Address</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Dirección</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="295"/> + <source>Status</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Estado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="296"/> + <source>Hostname</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Hostname</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="297"/> + <source>Version</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Versión</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="298"/> + <source>Rules</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Reglas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Time</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Hora</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Action</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Acción</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Duration</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Duración</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Node</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Nodo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>Enabled</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Habilitado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="304"/> + <source>Hits</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Total</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>Protocol</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Protocolo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="306"/> + <source>Process</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Aplicación</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="307"/> + <source>Destination</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Destino</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="308"/> + <source>Rule</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Regla</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="309"/> + <source>UserID</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">UserID</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="310"/> + <source>LastConnection</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">ÚltimaConexión</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="285"/> + <source>Name</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Pavadinimas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="286"/> + <source>Address</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Adresas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="287"/> + <source>Status</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>BÅ«sena</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="288"/> + <source>Hostname</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Hostname</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="386"/> + <source>Version</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Versija</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="383"/> + <source>Rules</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Taisyklės</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="292"/> + <source>Time</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Laikas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Action</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Veiksmas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="294"/> + <source>Duration</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Trukmė</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="295"/> + <source>Node</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Mazgas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="296"/> + <source>Enabled</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Ä®jungta</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="405"/> + <source>Hits</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Hits</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Protocol</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Protokolas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Process</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Procesas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Destination</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Paskirties vieta</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="306"/> + <source>Rule</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Taisyklė</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="307"/> + <source>UserID</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Vartotojo ID</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="308"/> + <source>LastConnection</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>PaskutinisPrisijungimas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Args</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Args</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>DstIP</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>PaskIP</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="304"/> + <source>DstHost</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>DstHost</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>DstPort</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>PaskPrievadas</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="652"/> + <source>New node connected</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="23"/> + <source>What</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="25"/> + <source>Network name</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="289"/> + <source>Uptime</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">Veikimo laikas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="297"/> + <source>Precedence</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="384"/> + <source>Connections</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="385"/> + <source>Dropped</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="404"/> + <source>What</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="736"/> + <source>Apply to</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="746"/> + <source>Reject</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>stats_deleterule</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="774"/> + <source> Your are about to delete this rule. </source> + <translation type="obsolete"> Estás a punto de borrar esta regla. </translation> + </message> +</context> +<context> + <name>stats_deleterule2</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="776"/> + <source> Are you sure?</source> + <translation type="obsolete"> ¿Estás seguro?</translation> + </message> +</context> +<context> + <name>stats_disabled</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="74"/> + <source>Disabled</source> + <translation type="obsolete">Deshabilitado</translation> + </message> +</context> +<context> + <name>stats_notrunning</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="73"/> + <source>Not running</source> + <translation type="obsolete">Parado</translation> + </message> +</context> +<context> + <name>stats_running</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="75"/> + <source>Running</source> + <translation type="obsolete">Interceptando</translation> + </message> +</context> +<context> + <name>stats_wintitle</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="409"/> + <source>OpenSnitch Network Statistics</source> + <translation type="obsolete">Eventos de red OpenSnitch</translation> + </message> +</context> +<context> + <name>stats_wintitle2</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="411"/> + <source>OpenSnitch Network Statistics for</source> + <translation type="obsolete">Eventos de OpenSnitch de</translation> + </message> +</context> +</TS> diff --git a/ui/i18n/locales/nb_NO/opensnitch-nb_NO.ts b/ui/i18n/locales/nb_NO/opensnitch-nb_NO.ts new file mode 100644 index 0000000..5589b38 --- /dev/null +++ b/ui/i18n/locales/nb_NO/opensnitch-nb_NO.ts @@ -0,0 +1,2390 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="nb_NO"> +<context> + <name>Dialog</name> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="34"/> + <source>opensnitch-qt</source> + <translation>opensnitch-qt</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="299"/> + <source>User ID</source> + <translation>Brukerid</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="333"/> + <source><html><head/><body><p><span style=" font-weight:600;">Executed from</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-weight:600;">Utført fra</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="630"/> + <source>TextLabel</source> + <translation>TextLabel</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="426"/> + <source>Source IP</source> + <translation>Kilde-IP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="449"/> + <source>Process ID</source> + <translation>Prosess-ID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="582"/> + <source>Destination IP</source> + <translation>MÃ¥l-IP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="605"/> + <source>Dst Port</source> + <translation>MÃ¥lport</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="679"/> + <source>from this executable</source> + <translation>fra denne kjørbare fil</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="684"/> + <source>from this command line</source> + <translation>fra denne kommandolinje</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="689"/> + <source>this destination port</source> + <translation>denne mÃ¥lport</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="694"/> + <source>this user</source> + <translation>denne bruker</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="699"/> + <source>this destination ip</source> + <translation>denne mÃ¥l-IP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="728"/> + <source>once</source> + <translation>en gang</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="733"/> + <source>30s</source> + <translation>30s</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="738"/> + <source>5m</source> + <translation>5m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="743"/> + <source>15m</source> + <translation>15m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="748"/> + <source>30m</source> + <translation>30m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="753"/> + <source>1h</source> + <translation>1t</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="706"/> + <source>for this session</source> + <translation type="obsolete">durante esta sesión</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="763"/> + <source>forever</source> + <translation>til evig tid</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="789"/> + <source>Deny</source> + <translation>Nekt</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="818"/> + <source>Allow</source> + <translation>Tillat</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="847"/> + <source>+</source> + <translation>+</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="758"/> + <source>until reboot</source> + <translation>til omstart</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="704"/> + <source>from this PID</source> + <translation>fra denne PID</translation> + </message> +</context> +<context> + <name>PreferencesDialog</name> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="14"/> + <source>Preferences</source> + <translation>Innstillinger</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="472"/> + <source>UI</source> + <translation>Grensesnitt</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="54"/> + <source><html><head/><body><p>This timeout is the countdown you see when a pop-up dialog is shown.</p></body></html></source> + <translation type="obsolete">Este timeout es la cuenta atrás que aparece cuando se muestra una ventana emergente</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="454"/> + <source>Default timeout</source> + <translation>Forvalgt utløpstid</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="331"/> + <source>Pop-up default duration</source> + <translation>Forvalgt varighet for sprettoppvindu</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="754"/> + <source>Default duration</source> + <translation>Forvalgt varighet</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="162"/> + <source>Pop-up default action</source> + <translation type="obsolete">Acción por defecto de la ventana emergente</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="483"/> + <source>Default action</source> + <translation type="obsolete">Acción por defecto</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="314"/> + <source>Default target</source> + <translation>Forvalgt mÃ¥l</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="170"/> + <source>center</source> + <translation>midtstilt</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="175"/> + <source>top right</source> + <translation>øvre høyre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="180"/> + <source>bottom right</source> + <translation>nedre høyre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="185"/> + <source>top left</source> + <translation>øvre venstre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="190"/> + <source>bottom left</source> + <translation>nedre venstre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="167"/> + <source>Prompt dialog default position on screen</source> + <translation type="obsolete">Posición por defecto</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="274"/> + <source>by executable</source> + <translation>av kjørbar fil</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="279"/> + <source>by command line</source> + <translation>av kommandolinje</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="284"/> + <source>by destination port</source> + <translation>av mÃ¥lport</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="289"/> + <source>by destination ip</source> + <translation>av mÃ¥l-IP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="294"/> + <source>by user id</source> + <translation>av brukerid</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="863"/> + <source>once</source> + <translation>en gang</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="210"/> + <source>30s</source> + <translation>30s</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="215"/> + <source>5m</source> + <translation>5m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="220"/> + <source>15m</source> + <translation>15m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="225"/> + <source>30m</source> + <translation>30m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="230"/> + <source>1h</source> + <translation>1t</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="240"/> + <source>for this session</source> + <translation type="obsolete">durante esta sesión</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="240"/> + <source>forever</source> + <translation>til evig tid</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="905"/> + <source>deny</source> + <translation>nekt</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="914"/> + <source>allow</source> + <translation>tillat</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="406"/> + <source>Disable pop-ups, only display an alert</source> + <translation type="obsolete">Deshabilitar ventanas emergentes, +sólo mostrar alerta</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="711"/> + <source>Nodes</source> + <translation>Noder</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="717"/> + <source>Process monitor method</source> + <translation>Prosessmonitoreringsmetode</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="751"/> + <source><html><head/><body><p>The default duration will take place when there's no UI connected.</p></body></html></source> + <translation><html><head/><body><p>Forvalgt varighet tar effekt nÃ¥r det ikke er noe brukergrensesnitt tilkoblet.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="881"/> + <source><html><head/><body><p>Address of the node.</p><p>Default: unix:///tmp/osui.sock (unix:// is mandatory if it's a Unix socket)</p><p>It can also be an IP address with the port: 127.0.0.1:50051</p></body></html></source> + <translation><html><head/><body><p>Adressen til noden.</p><p>Forvalgt: unix:///tmp/osui.sock (unix:// er pÃ¥krevd hvis det er en Unix-socket)</p><p>Det kan ogsÃ¥ være en IP-adresse med port: 127.0.0.1:50051</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="884"/> + <source>Address</source> + <translation>Adresse</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1024"/> + <source>Default log level</source> + <translation>Forvalgt loggnivÃ¥</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="932"/> + <source>Version</source> + <translation>Versjon</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="790"/> + <source><html><head/><body><p>The default action will take place when there's no UI connected.</p></body></html></source> + <translation><html><head/><body><p>Forvalgt handling nÃ¥r det ikke er noe brukergrensesnitt tilkoblet.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="734"/> + <source><html><head/><body><p>Log file to write logs.<br/></p><p>/dev/stdout will print logs to the standard output.</p></body></html></source> + <translation><html><head/><body><p>Loggfiler Ã¥ skrive logger til.<br/></p><p>/dev/stdout skriver logger til standard-ut.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="737"/> + <source>Log file</source> + <translation>Loggfil</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="578"/> + <source><html><head/><body><p>If checked, opensnitch will prompt you to allow or deny connections that don't have an asocciated PID, due to several reasons.</p><p>The pop-up dialog will only contain information about the network connection.</p></body></html></source> + <translation type="obsolete">Si marcas esta opción, OpenSnitch te preguntará para Aceptar o Denegar conexiones que no tengan un PID asociado por diferentes razones. + +La ventana emergente sólo contendrá información relativa a la conexión. + +Nota: Estas conexiones no tienen por qué indicar que algo sospechoso está sucediendo. Simplemente +es que no hemos descubierto el PID (por ejemplo conexiones que no se originan en la máquina, o paquetes en mal estado).</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="581"/> + <source>Intercept Unknown Connections</source> + <translation type="obsolete">Interceptar conexiones desconocidas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="809"/> + <source>HostName</source> + <translation>HostName</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="983"/> + <source>unix:///tmp/osui.sock</source> + <translation>unix:///tmp/osui.sock</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="868"/> + <source>until restart</source> + <translation>frem til omstart</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="873"/> + <source>always</source> + <translation>alltid</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="995"/> + <source>/var/log/opensnitchd.log</source> + <translation>/var/log/opensnitchd.log</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1000"/> + <source>/dev/stdout</source> + <translation>/dev/stdout</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="767"/> + <source>Apply configuration to all nodes</source> + <translation>Anvend oppsett for alle noder</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1039"/> + <source>Database</source> + <translation>Database</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1074"/> + <source>In memory</source> + <translation>I minnet</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1079"/> + <source>File</source> + <translation>Fil</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1345"/> + <source>Close</source> + <translation>Lukk</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1356"/> + <source>Apply</source> + <translation>Anvend</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1367"/> + <source>Save</source> + <translation>Lagre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="235"/> + <source>until reboot</source> + <translation>frem til omstart</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1093"/> + <source>Database type</source> + <translation>Databasetype</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1100"/> + <source>Select</source> + <translation>Velg</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="83"/> + <source>Pop-ups default options</source> + <translation type="obsolete">Opciones por defecto de las ventanas emergentes</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="367"/> + <source>Pop-ups default position on screen</source> + <translation type="obsolete">Posición en pantalla</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="102"/> + <source><html><head/><body><p>The advanced view allows you to apply more filters on a connection</p><p>when a pop-up appears.</p></body></html></source> + <translation type="obsolete">La vista avanzada permite filtrar conexiones por más parámetros</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="350"/> + <source>Show advanced view by default</source> + <translation>Vis avansert fremvisning som forvalg</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="665"/> + <source>Action</source> + <translation>Handling</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="366"/> + <source><html><head/><body><p>If checked, the pop-ups will be displayed with the advanced view active.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="334"/> + <source>Duration</source> + <translation>Varighet</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="254"/> + <source><html><head/><body><p>By default when a new pop-up appears, in its simplest form, you'll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).</p><p>With these options, you can choose multiple fields to filter connections for.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="257"/> + <source>Filter connections also by:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="362"/> + <source>If checked, this field will be checked when a pop-up is displayed</source> + <translation type="obsolete">Si lo seleccionas, este campo se usará para filtrar las conexiones</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="81"/> + <source>User ID</source> + <translation>Bruker-ID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="97"/> + <source>Destination port</source> + <translation>MÃ¥lport</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="113"/> + <source>Destination IP</source> + <translation>MÃ¥l-IP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="451"/> + <source><html><head/><body><p>This timeout is the countdown you see when a pop-up dialog is shown.</p><p>If the pop-up is not answered, the default options will be applied.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="347"/> + <source>The advanced view allows you to easily select multiple fields to filter connections</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="110"/> + <source>If checked, this field will be selected when a pop-up is displayed</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="150"/> + <source><html><head/><body><p>Pop-up default action.</p><p>When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.</p><p><br/></p><p>While a pop-up is asking the user to allow or deny a connection:</p><p>1. new outgoing connections are denied.</p><p>2. known connections are allowed or denied based on the rules defined by the user.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="793"/> + <source>Default action when the GUI is disconnected</source> + <translation>Forvalgt handling nÃ¥r det grafiske brukergrensesnittet er frakoblet</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="894"/> + <source>Debug invalid connections</source> + <translation>Feilsøk ugyldige forbindelser</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="39"/> + <source>Pop-ups</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="64"/> + <source>Default options</source> + <translation>Forvalgte innstillinger</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="321"/> + <source>Default position on screen</source> + <translation>Forvalgt posisjon pÃ¥ skjermen</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="479"/> + <source>any temporary rules</source> + <translation>alle midertidige regler</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="492"/> + <source><html><head/><body><p>When this option is selected, the rules of the selected duration won't be added to the list of temporary rules in the GUI.</p><p><br/></p><p>Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="495"/> + <source>Don't save rules of duration</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="463"/> + <source>Show events columns</source> + <translation type="obsolete">Mostrar columnas de la pestaña Eventos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="601"/> + <source>Time</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="681"/> + <source>Destination</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="649"/> + <source>Protocol</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="697"/> + <source>Process</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="617"/> + <source>Rule</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="633"/> + <source>Node</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="723"/> + <source><html><head/><body><p>If checked, opensnitch will prompt you to allow or deny connections that don't have an asocciated PID, due to several reasons, mostly due to bad state connections.</p><p>The pop-up dialog will only contain information about the network connection.</p><p>There're some scenarios where these are valid connections though, like when establishing a VPN using wireguard.</p></body></html></source> + <translation type="obsolete"><html><head/><body><p>Si se selecciona opensnitch te preguntará para permitir o denegar conexiones que no tienen un PID asociado. Esto puede pasar por diferentes motivos, principalmente debido a conexiones inválidas.</p><p>La ventana emergente sólo contendrá información de la conexión.</p><p>Hay algunas situaciones en las que estas conexiones son válidas, por ejemplo al establecer un túnel VPN con wireguard.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="589"/> + <source>Events tab columns</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="299"/> + <source>by PID</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="461"/> + <source>Disable pop-ups, only display an notification</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="508"/> + <source>Desktop notifications</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="526"/> + <source>Use system notifications</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="542"/> + <source>Use Qt notifications</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="571"/> + <source>Test</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="891"/> + <source><html><head/><body><p>If checked, OpenSnitch will prompt you to allow or deny connections that don't have an associated PID, due to several reasons, mostly due to bad state connections.</p><p>The pop-up dialog will only contain information about the network connection.</p><p>There're some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1178"/> + <source>minutes</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1204"/> + <source>Minutes between events purges</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1227"/> + <source>days</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1237"/> + <source>Maximum days of events to keep</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>ProcessDetailsDialog</name> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="14"/> + <source>Process details</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="61"/> + <source>loading...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="81"/> + <source>CWD: loading...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="93"/> + <source>mem stats: loading...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="121"/> + <source>Status</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="135"/> + <source>Open files</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="149"/> + <source>I/O Statistics</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="163"/> + <source>Memory mapped files</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="177"/> + <source>Stack</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="191"/> + <source>Environment variables</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="210"/> + <source>Application pids</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="240"/> + <source>Start or stop monitoring this process</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="256"/> + <source>Close</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>RulesDialog</name> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="14"/> + <source>Rule</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="118"/> + <source>Node</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="141"/> + <source>Apply rule to all nodes</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="239"/> + <source>From this command line</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="206"/> + <source>From this executable</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="751"/> + <source>Action</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="138"/> + <source>/path/to/executable, .*/bin/executable[0-9\.]+$, ...</source> + <translation type="obsolete">/ruta/al/ejecutable, .*/bin/executable[0-9\.]+$, ...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="383"/> + <source>To this IP / Network</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="792"/> + <source>once</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="797"/> + <source>30s</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="802"/> + <source>5m</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="807"/> + <source>15m</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="812"/> + <source>30m</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="817"/> + <source>1h</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="230"/> + <source>until restart</source> + <translation type="obsolete">hasta reiniciar (el servicio)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="827"/> + <source>always</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="293"/> + <source>To this port</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="169"/> + <source>From this user ID</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="419"/> + <source>Commas or spaces are not allowed to specify multiple domains. + +Use regular expressions instead: +.*(opensnitch|duckduckgo).com +.*\.google.com + +or a single domain: +www.gnu.org - it'll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ... +gnu.org - it'll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="430"/> + <source>www.domain.org, .*\.domain.org</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="323"/> + <source><html><head/><body><p>Only TCP, UDP or UDPLITE are allowed</p><p>You can use regexp, i.e.: ^(TCP|UDP)$</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="333"/> + <source>TCP</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="338"/> + <source>UDP</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="343"/> + <source>UDPLITE</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="348"/> + <source>TCP6</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="353"/> + <source>UDP6</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="358"/> + <source>UDPLITE6</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="440"/> + <source>You can specify a single IP: +- 192.168.1.1 + +or a regular expression: +- 192\.168\.1\.[0-9]+ + +multiple IPs: +- ^(192\.168\.1\.1|172\.16\.0\.1)$ + +You can also specify a subnet: +- 192.168.1.0/24 + +Note: Commas or spaces are not allowed to separate IPs or networks.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="459"/> + <source>LAN</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="464"/> + <source>127.0.0.0/8</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="469"/> + <source>192.168.0.0/24</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="474"/> + <source>192.168.1.0/24</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="479"/> + <source>192.168.2.0/24</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="484"/> + <source>192.168.0.0/16</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="489"/> + <source>169.254.0.0/16</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="494"/> + <source>172.16.0.0/12</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="499"/> + <source>10.0.0.0/8</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="504"/> + <source>::1/128</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="509"/> + <source>fc00::/7</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="514"/> + <source>ff00::/8</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="519"/> + <source>fe80::/10</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="524"/> + <source>fd00::/8</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="784"/> + <source>Duration</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="376"/> + <source>Protocol</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="300"/> + <source>To this host</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="843"/> + <source>Deny</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="877"/> + <source>Allow</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="899"/> + <source>Name</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="22"/> + <source>Enable</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="706"/> + <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them. + +000-allow-localhost +001-deny-broadcast +...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="713"/> + <source>leave blank to autocreate</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="46"/> + <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one. + +You must name the rule in such manner that it'll be checked first, because they're checked in alphabetical order. For example: + +[x] Priority - 000-priority-rule +[ ] Priority - 001-less-priority-rule</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="54"/> + <source>Priority rule</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="74"/> + <source><html><head/><body><p>By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.<br/></p><p>If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="77"/> + <source>Case-sensitive</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="369"/> + <source><html><head/><body><p>You can specify multiple ports using regular expressions:</p><p><br/></p><p>- 53, 80 or 443:</p><p>^(53|80|443)$</p><p><br/></p><p>- 53, 443 or 5551, 5552, 5553, etc:</p><p>^(53|443|555[0-9])$</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="822"/> + <source>until reboot</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="583"/> + <source>To this list of domains</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="539"/> + <source><html><head/><body><p>Select a directory with lists of domains to block or allow.</p><p>Put inside that directory files with any extension containing lists of domains.</p><p><br/>The format of each entry of a list is as follow (hosts format):</p><p>127.0.0.1 www.domain.com</p><p>or </p><p>0.0.0.0 www.domain.com</p></body></html></source> + <translation type="obsolete"><html><head/><body><p>Selecciona un directorio con listas de dominios para permitir o denegar.</p><p>Mete dentro de este directorio ficheros con cualquier extensión que contengan listas de dominios.</p><p><br/>El formato de cada dominio de la lista tiene que estar en formato hosts, así:</p><p>127.0.0.1 www.domain.com</p><p>o </p><p>0.0.0.0 www.domain.com</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="163"/> + <source>Applications</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="216"/> + <source><html><head/><body><p>This field will only match the executable path. It is not modifiable by the user.<br/></p><p>You can use regular expressions to deny executions from /tmp for example:<br/></p><p>^/tmp/.*$</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="229"/> + <source><html><head/><body><p>This field will contain and match the command line that was executed by the user.<br/></p><p>If the user typed the command, only the command will appear:</p><p>telnet 1.2.3.4<br/></p><p>If the user typed the absolute or relative path to the command, that is what will appear:</p><p>/usr/bin/telnet 1.2.3.4</p><p>../../../usr/bin/telnet 1.2.3.4</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="246"/> + <source>From this PID</source> + <translation>Fra denne PID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="287"/> + <source>Network</source> + <translation>Nettverk</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="536"/> + <source>List of domains/IPs</source> + <translation>Liste med domener/IP-nummer</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="542"/> + <source>To this list of network ranges</source> + <translation>Til denne listen med nettverkomrÃ¥der</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="549"/> + <source>To this list of IPs</source> + <translation>Til denne listen med IP-adresser</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="574"/> + <source><html><head/><body><p>Select a directory with files containing list of IPs to block or allow:</p><p>1.2.3.4.5</p><p>1.2.3.4.6</p><p>.</p><p>etc.</p><p>One IP per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="608"/> + <source><html><head/><body><p>Select a directory with files containing list of network ranges to block or allow:</p><p>1.2.3.0/24</p><p>80.34.56.0/20</p><p>.</p><p>etc.<br/></p><p>One Network Range per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="636"/> + <source><html><head/><body><p>Select a directory with lists of domains to block or allow.</p><p>Put inside that directory files with any extension containing lists of domains.</p><p><br/>The format of each entry of a list is as follow (hosts format):</p><p>127.0.0.1 www.domain.com</p><p>or </p><p>0.0.0.0 www.domain.com</p><p>Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="651"/> + <source>To this list of domains +(regular expressions)</source> + <translation>Til denne listen med domener +(regulæruttrykk)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="677"/> + <source><html><head/><body><p>Select a directory with files containing regular expressions of domains to block or allow:</p><p>.*\.example\.com</p><p>You can also use a domain as is: &quot;example.com&quot; , and it'll match whatever.example.com, whatever.example.com.localdomain, etc.</p><p>One domain per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="857"/> + <source>Reject</source> + <translation>Avvis</translation> + </message> +</context> +<context> + <name>StatsDialog</name> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="34"/> + <source>OpenSnitch Network Statistics</source> + <translation>OpenSnitch nettverkstatistikk</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="290"/> + <source>Save to CSV.</source> + <translation>Lagre til CSV.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="300"/> + <source>Ctrl+S</source> + <translation>Ctrl+S</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="351"/> + <source>Create a new rule</source> + <translation>Lag ny regel</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="381"/> + <source><html><head/><body><p><span style=" font-size:11pt; font-weight:600;">hostname - 192.168.1.1</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-size:11pt; font-weight:600;">vertsnavn - 192.168.1.1</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="420"/> + <source>Status</source> + <translation>Status</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1665"/> + <source>-</source> + <translation>-</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="464"/> + <source>Start or Stop interception</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="509"/> + <source>Events</source> + <translation>Hendelser</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="94"/> + <source>Filter</source> + <translation>Filter</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="107"/> + <source>Allow</source> + <translation>Tillat</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="116"/> + <source>Deny</source> + <translation>Nekt</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="143"/> + <source>Ex.: firefox</source> + <translation>F.eks.: firefox</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="199"/> + <source>50</source> + <translation>50</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="204"/> + <source>100</source> + <translation>100</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="209"/> + <source>200</source> + <translation>200</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="214"/> + <source>300</source> + <translation>300</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="748"/> + <source>Nodes</source> + <translation>Noder</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="554"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on the Addr column to view details of a node)</span></p></body></html></source> + <translation type="obsolete">(doble click en la columna Dirección para ver los detalles)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1569"/> + <source>Rules</source> + <translation>Regler</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="857"/> + <source>enable</source> + <translation>aktiver</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="684"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on the Name column to view details of a rule)</span></p></body></html></source> + <translation type="obsolete">(doble click en la columna Nombre para ver los detalles)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="692"/> + <source>search rule name</source> + <translation type="obsolete">buscar regla</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="704"/> + <source>Application rules</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="806"/> + <source>Permanent</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="815"/> + <source>Temporary</source> + <translation>Midlertidig</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="933"/> + <source>Hosts</source> + <translation>Verter</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1364"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click to view details of an item)</span></p></body></html></source> + <translation type="obsolete">(doble click en un dominio para ver detalles)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1020"/> + <source>Applications</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1127"/> + <source>Addresses</source> + <translation>Adresser</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1214"/> + <source>Ports</source> + <translation>Porter</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1298"/> + <source>Users</source> + <translation>Brukere</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1404"/> + <source>Connections</source> + <translation>Forbindelser</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1459"/> + <source>Dropped</source> + <translation>Droppet</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1514"/> + <source>Uptime</source> + <translation>Oppetid</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1639"/> + <source>Version</source> + <translation>Versjon</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="227"/> + <source>Delete all intercepted events</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="864"/> + <source>Edit rule</source> + <translation>Endre regel</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="878"/> + <source>Delete rule</source> + <translation>Slett regel</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="926"/> + <source>Delete all intercepted hosts</source> + <translation type="obsolete">Borrar todos los hosts</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1051"/> + <source>Delete all intercepted applications</source> + <translation type="obsolete">Borrar todos las aplicaciones</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1159"/> + <source>Delete all intercepted addresses</source> + <translation type="obsolete">Borrar todas las direcciones</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1261"/> + <source>Delete all intercepted ports</source> + <translation type="obsolete">Borrar todos los puertos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1371"/> + <source>Delete all intercepted users</source> + <translation type="obsolete">Borrar todos los usuarios</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="699"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on a row to view details of a rule)</span></p></body></html></source> + <translation type="obsolete">(Doble click en una fila para editar una regla)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="912"/> + <source>Delete connections that matched this rule</source> + <translation>Slett forbindelser som passer til denne regel</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="797"/> + <source>All applications</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="125"/> + <source>Reject</source> + <translation>Avvis</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="177"/> + <source>0</source> + <translation>0</translation> + </message> +</context> +<context> + <name>contextual_menu</name> + <message> + <location filename="../../../opensnitch/service.py" line="43"/> + <source>Statistics</source> + <translation>Statistikk</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="46"/> + <source>Help</source> + <translation>Hjelp</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="47"/> + <source>Close</source> + <translation>Lukk</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="44"/> + <source>Enable</source> + <translation>Aktiver</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="45"/> + <source>Disable</source> + <translation>Deaktiver</translation> + </message> +</context> +<context> + <name>menu_close</name> + <message> + <location filename="../../../opensnitch/service.py" line="131"/> + <source>Close</source> + <translation type="obsolete">Cerrar</translation> + </message> +</context> +<context> + <name>menu_help</name> + <message> + <location filename="../../../opensnitch/service.py" line="126"/> + <source>Help</source> + <translation type="obsolete">Ayuda</translation> + </message> +</context> +<context> + <name>menu_statistics</name> + <message> + <location filename="../../../opensnitch/service.py" line="120"/> + <source>Statistics</source> + <translation type="obsolete">Eventos</translation> + </message> +</context> +<context> + <name>notifications</name> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="518"/> + <source>System notifications are not available, you need to install python3-notify2.</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>popups</name> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="91"/> + <source>Allow</source> + <translation>Tillat</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="92"/> + <source>Deny</source> + <translation>Nekt</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="54"/> + <source>forever</source> + <translation>for alltid</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="285"/> + <source>Outgoing connection</source> + <translation>UtgÃ¥ende forbindelse</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="290"/> + <source>Process launched from:</source> + <translation>Prosess startet fra:</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="325"/> + <source>from this command line</source> + <translation>fra denne kommandolinjen</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="321"/> + <source>from this executable</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="208"/> + <source>Unknown process</source> + <translation type="obsolete">Proceso no encontrado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="52"/> + <source>until reboot</source> + <translation>frem til omstart</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="327"/> + <source>to port {0}</source> + <translation>til port {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="222"/> + <source><b>%s</b> is connecting to <b>%s</b> on %s port %d</source> + <translation type="obsolete"><b>%s</b> está conectándose a <b>%s</b> en el puerto %s %d</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="228"/> + <source><b>Remote</b> process <b>%s</b> running on <b>%s</b> is connecting to <b>%s</b> on %s port %d</source> + <translation type="obsolete">El proceso <b>remoto %s</b> ejecutándose en <b>%s</b> está conectándose a <b>%s</b> en el puerto %s %d</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="390"/> + <source>to {0}</source> + <translation>til {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="330"/> + <source>from user {0}</source> + <translation>fra bruker {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="347"/> + <source>to {0}.*</source> + <translation>til {0}.*</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="400"/> + <source>to *.{0}</source> + <translation>til *.{0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="378"/> + <source>to *{0}</source> + <translation type="obsolete">a *{0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="434"/> + <source><b>Remote</b> process %s running on <b>%s</b></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="438"/> + <source>is connecting to <b>%s</b> on %s port %d</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="444"/> + <source>is attempting to resolve <b>%s</b> via %s, %s port %d</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="334"/> + <source>from this PID</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/notifications.py" line="105"/> + <source>New outgoing connection</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>popups2</name> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="254"/> + <source><b>Remote</b> process <b>%s</b> running on <b>%s</b> is connecting to <b>%s</b> on %s port %d</source> + <translation type="obsolete">El proceso <b>remoto %s</b> ejecutándose en <b>%s</b> está conectándose a <b>%s</b> en el puerto %s %d</translation> + </message> +</context> +<context> + <name>preferences</name> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="171"/> + <source>Exception saving config: %s</source> + <translation type="obsolete">Error al guarda la configuración: %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="177"/> + <source>Applying configuration on %s ...</source> + <translation type="obsolete">Aplicando configuración en %s ...</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="208"/> + <source>Server address can not be empty</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="227"/> + <source>Error loading %s configuration</source> + <translation type="obsolete">Error al cargar la configuración %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="448"/> + <source>Configuration applied.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="257"/> + <source>Error applying configuration: %s</source> + <translation type="obsolete">Error al aplicar la configuración: %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="299"/> + <source>Exception saving config: {0}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="389"/> + <source>Applying configuration on {0} ...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="238"/> + <source>Error loading {0} configuration</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="450"/> + <source>Error applying configuration: {0}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="323"/> + <source>Warning</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="323"/> + <source>You must select a file for the database<br>or choose "In memory" type.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="329"/> + <source>DB type changed</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="329"/> + <source>Restart the GUI in order effects to take effect</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="480"/> + <source>Hover the mouse over the texts to display the help<br><br>Don't forget to visit the wiki: <a href="{0}">{0}</a></source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>proc_details</name> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="97"/> + <source><b>Error loading process information:</b> <br><br> + +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="116"/> + <source><b>Error stopping monitoring process:</b><br><br></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="156"/> + <source>loading...</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>rules</name> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="162"/> + <source>There're no nodes connected.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="203"/> + <source>Rule applied.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="123"/> + <source>Error applying rule: %s</source> + <translation type="obsolete">Error al aplicar la regla: %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="527"/> + <source>protocol can not be empty, or uncheck it</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="541"/> + <source>Protocol regexp error</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="545"/> + <source>process path can not be empty</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="559"/> + <source>Process path regexp error</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="563"/> + <source>command line can not be empty</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="577"/> + <source>Command line regexp error</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="581"/> + <source>Dest port can not be empty</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="595"/> + <source>Dst port regexp error</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="599"/> + <source>Dest host can not be empty</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="613"/> + <source>Dst host regexp error</source> + <translation>Feil med mÃ¥lvert-regulæruttrykk</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="617"/> + <source>Dest IP/Network can not be empty</source> + <translation>MÃ¥l-IP/nettverk kan ikke være blank</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="639"/> + <source>Dst IP regexp error</source> + <translation>Feil med regulæruttrykk for mÃ¥l-IP</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="651"/> + <source>User ID can not be empty</source> + <translation>Bruker-ID kan ikke være blank</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="665"/> + <source>User ID regexp error</source> + <translation>Feil med regulæruttrykk for bruker-ID</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="205"/> + <source>Error applying rule: {0}</source> + <translation>Feil ved anvending av regel: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="739"/> + <source>Lists field cannot be empty</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="741"/> + <source>Lists field must be a directory</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="777"/> + <source><b>Rule not supported</b></source> + <translation><b>Regelen støttes ikke</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="429"/> + <source><b>Error loading rule</b></source> + <translation><b>Feil ved regellasting</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="179"/> + <source>There's already a rule with this name.</source> + <translation>Det er allerede en regel med dette navnet.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="669"/> + <source>PID field can not be empty</source> + <translation>PID-feltet kan ikke være blankt</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="683"/> + <source>PID field regexp error</source> + <translation>Feil med regulæruttrykk for PID-felt</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="764"/> + <source>Select at least one field.</source> + <translation>Velg minst ett felt.</translation> + </message> +</context> +<context> + <name>stats</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="310"/> + <source>Not running</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="311"/> + <source>Disabled</source> + <translation>Utkoblet</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="312"/> + <source>Running</source> + <translation>Kjører</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="412"/> + <source>OpenSnitch Network Statistics</source> + <translation type="obsolete">Eventos de OpenSnitch</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="414"/> + <source>OpenSnitch Network Statistics for</source> + <translation type="obsolete">Eventos de OpenSnitch de</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="761"/> + <source> Your are about to delete this rule. </source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1244"/> + <source> Are you sure?</source> + <translation> Er du sikker?</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="568"/> + <source>OpenSnitch Network Statistics {0}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="570"/> + <source>OpenSnitch Network Statistics for {0}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <translation type="obsolete">Nombre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="294"/> + <source>Address</source> + <translation type="obsolete">Dirección</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="176"/> + <source>Status</source> + <translation type="obsolete">Estado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="177"/> + <source>Hostname</source> + <translation type="obsolete">Hostname</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="183"/> + <source>Version</source> + <translation type="obsolete">Versión</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="180"/> + <source>Rules</source> + <translation type="obsolete">Reglas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Time</source> + <translation type="obsolete">Hora</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Action</source> + <translation type="obsolete">Acción</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Duration</source> + <translation type="obsolete">Duración</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Node</source> + <translation type="obsolete">Nodo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="253"/> + <source>Hits</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>Protocol</source> + <translation type="obsolete">Protocolo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1837"/> + <source>Save as CSV</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>Enabled</source> + <translation type="obsolete">Habilitado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="738"/> + <source>Delete</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="948"/> + <source>always</source> + <translation type="obsolete">siempre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="580"/> + <source><b>Error:</b><br><br>{0}</source> + <translation type="obsolete"><b>Error:</b><br><br>{0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="731"/> + <source>Disable</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="733"/> + <source>Enable</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="736"/> + <source>Duplicate</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="737"/> + <source>Edit</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="891"/> + <source>Rule not found by that name and node</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="921"/> + <source><b>Error:</b><br><br></source> + <comment>{0}</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="928"/> + <source>Warning:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="717"/> + <source>Allow</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="718"/> + <source>Deny</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="722"/> + <source>Always</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="723"/> + <source>Until reboot</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1244"/> + <source> You are about to delete this rule. </source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="306"/> + <source>Process</source> + <translation type="obsolete">Aplicación</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="307"/> + <source>Destination</source> + <translation type="obsolete">Destino</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="308"/> + <source>Rule</source> + <translation type="obsolete">Regla</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="309"/> + <source>UserID</source> + <translation type="obsolete">UserID</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="174"/> + <source>LastConnection</source> + <translation type="obsolete">ÚltimaConexión</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <comment>xxxxx</comment> + <translation type="obsolete">Nombre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Nombre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="294"/> + <source>Address</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Dirección</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="295"/> + <source>Status</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Estado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="296"/> + <source>Hostname</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Hostname</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="297"/> + <source>Version</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Versión</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="298"/> + <source>Rules</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Reglas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Time</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Hora</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Action</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Acción</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Duration</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Duración</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Node</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Nodo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>Enabled</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Habilitado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="304"/> + <source>Hits</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Total</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>Protocol</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Protocolo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="306"/> + <source>Process</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Aplicación</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="307"/> + <source>Destination</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Destino</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="308"/> + <source>Rule</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Regla</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="309"/> + <source>UserID</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">UserID</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="310"/> + <source>LastConnection</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">ÚltimaConexión</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="392"/> + <source>Name</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="288"/> + <source>Address</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="379"/> + <source>Status</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="380"/> + <source>Hostname</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="386"/> + <source>Version</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="383"/> + <source>Rules</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="390"/> + <source>Time</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="395"/> + <source>Action</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="396"/> + <source>Duration</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="391"/> + <source>Node</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="393"/> + <source>Enabled</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="405"/> + <source>Hits</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Protocol</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Process</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Destination</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="306"/> + <source>Rule</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="307"/> + <source>UserID</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="377"/> + <source>LastConnection</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Args</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>DstIP</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="304"/> + <source>DstHost</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>DstPort</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="175"/> + <source>Addr</source> + <translation type="obsolete">Dirección</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="181"/> + <source>Connections</source> + <translation type="obsolete">Conexiones</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="182"/> + <source>Dropped</source> + <translation type="obsolete">Rechazadas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="252"/> + <source>What</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="709"/> + <source>Apply to</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="719"/> + <source>Reject</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="25"/> + <source>Network name</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="378"/> + <source>Addr</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Adr.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="382"/> + <source>Uptime</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Oppetid</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="384"/> + <source>Connections</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Forbindelser</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="385"/> + <source>Dropped</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Droppet</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="404"/> + <source>What</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Hva</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="394"/> + <source>Precedence</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Prosedyre</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="644"/> + <source>New node connected</source> + <translation>Koblet opp ny node</translation> + </message> +</context> +<context> + <name>stats_deleterule</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="774"/> + <source> Your are about to delete this rule. </source> + <translation type="obsolete"> Estás a punto de borrar esta regla. </translation> + </message> +</context> +<context> + <name>stats_deleterule2</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="776"/> + <source> Are you sure?</source> + <translation type="obsolete"> ¿Estás seguro?</translation> + </message> +</context> +<context> + <name>stats_disabled</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="74"/> + <source>Disabled</source> + <translation type="obsolete">Deshabilitado</translation> + </message> +</context> +<context> + <name>stats_notrunning</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="73"/> + <source>Not running</source> + <translation type="obsolete">Parado</translation> + </message> +</context> +<context> + <name>stats_running</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="75"/> + <source>Running</source> + <translation type="obsolete">Interceptando</translation> + </message> +</context> +<context> + <name>stats_wintitle</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="409"/> + <source>OpenSnitch Network Statistics</source> + <translation type="obsolete">Eventos de red OpenSnitch</translation> + </message> +</context> +<context> + <name>stats_wintitle2</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="411"/> + <source>OpenSnitch Network Statistics for</source> + <translation type="obsolete">Eventos de OpenSnitch de</translation> + </message> +</context> +</TS> diff --git a/ui/i18n/locales/pt_BR/opensnitch-pt_BR.ts b/ui/i18n/locales/pt_BR/opensnitch-pt_BR.ts new file mode 100644 index 0000000..79aa184 --- /dev/null +++ b/ui/i18n/locales/pt_BR/opensnitch-pt_BR.ts @@ -0,0 +1,2248 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="pt_BR"> +<context> + <name>Dialog</name> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="34"/> + <source>opensnitch-qt</source> + <translation>opensnitch-qt</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="299"/> + <source>User ID</source> + <translation>ID do usuário</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="333"/> + <source><html><head/><body><p><span style=" font-weight:600;">Executed from</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-weight:600;">Executado de</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="630"/> + <source>TextLabel</source> + <translation>TextLabel</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="426"/> + <source>Source IP</source> + <translation>IP de origem</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="449"/> + <source>Process ID</source> + <translation>ID de processo</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="582"/> + <source>Destination IP</source> + <translation>IP de destino</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="605"/> + <source>Dst Port</source> + <translation>Porta Dst</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="226"/> + <source>(/path/to/bin/chromium)</source> + <translation type="obsolete">(/caminho/para/bin/chromium)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="271"/> + <source>Chromium Web Browser wants to connect to www.evilsocket.net on tcp port 443. And maybe to www.goodsocket.net on port 344</source> + <translation type="obsolete">O navegador da Web Chromium deseja se conectar a www.evilsocket.net na porta tcp 443. E talvez a www.goodsocket.net na porta 344</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="679"/> + <source>from this executable</source> + <translation>a partir deste executável</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="684"/> + <source>from this command line</source> + <translation>a partir desta linha de comando</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="689"/> + <source>this destination port</source> + <translation>esta porta de destino</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="694"/> + <source>this user</source> + <translation>este usuário</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="699"/> + <source>this destination ip</source> + <translation>este ip de destino</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="728"/> + <source>once</source> + <translation>uma vez</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="733"/> + <source>30s</source> + <translation>30s</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="738"/> + <source>5m</source> + <translation>5m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="743"/> + <source>15m</source> + <translation>15m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="748"/> + <source>30m</source> + <translation>30m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="753"/> + <source>1h</source> + <translation>1h</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="706"/> + <source>for this session</source> + <translation type="obsolete">para esta sessão</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="763"/> + <source>forever</source> + <translation>para sempre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="789"/> + <source>Deny</source> + <translation>Negar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="818"/> + <source>Allow</source> + <translation>Permitir</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="847"/> + <source>+</source> + <translation>+</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="758"/> + <source>until reboot</source> + <translation>até reiniciar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="704"/> + <source>from this PID</source> + <translation>a partir desse PID</translation> + </message> +</context> +<context> + <name>New node connected</name> +</context> +<context> + <name>PreferencesDialog</name> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="14"/> + <source>Preferences</source> + <translation>Preferências</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="472"/> + <source>UI</source> + <translation>UI</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="54"/> + <source><html><head/><body><p>This timeout is the countdown you see when a pop-up dialog is shown.</p></body></html></source> + <translation type="obsolete"><html><head/><body><p>Este tempo limite é a contagem regressiva que você vê quando uma caixa de diálogo pop-up é exibida.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="454"/> + <source>Default timeout</source> + <translation>Tempo limite padrão</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="331"/> + <source>Pop-up default duration</source> + <translation>Duração padrão do pop-up</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="754"/> + <source>Default duration</source> + <translation>Duração padrão</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="162"/> + <source>Pop-up default action</source> + <translation type="obsolete">Ação padrão de pop-up</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="483"/> + <source>Default action</source> + <translation type="obsolete">Ação padrão</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="314"/> + <source>Default target</source> + <translation>Alvo padrão</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="170"/> + <source>center</source> + <translation>centro</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="175"/> + <source>top right</source> + <translation>superior direito</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="180"/> + <source>bottom right</source> + <translation>inferior direito</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="185"/> + <source>top left</source> + <translation>superior esquerdo</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="190"/> + <source>bottom left</source> + <translation>inferior esquerdo</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="167"/> + <source>Prompt dialog default position on screen</source> + <translation type="obsolete">Posição padrão da caixa de diálogo de prompt na tela</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="274"/> + <source>by executable</source> + <translation>por executável</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="279"/> + <source>by command line</source> + <translation>por linha de comando</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="284"/> + <source>by destination port</source> + <translation>por porta de destino</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="289"/> + <source>by destination ip</source> + <translation>por ip de destino</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="294"/> + <source>by user id</source> + <translation>por id de usuário</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="863"/> + <source>once</source> + <translation>uma vez</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="210"/> + <source>30s</source> + <translation>30s</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="215"/> + <source>5m</source> + <translation>5m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="220"/> + <source>15m</source> + <translation>15m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="225"/> + <source>30m</source> + <translation>30m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="230"/> + <source>1h</source> + <translation>1h</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="240"/> + <source>for this session</source> + <translation type="obsolete">para esta sessão</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="240"/> + <source>forever</source> + <translation>para sempre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="905"/> + <source>deny</source> + <translation>negar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="914"/> + <source>allow</source> + <translation>permitir</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="411"/> + <source>Disable pop-ups, only display an alert</source> + <translation type="obsolete">Desativar pop-ups, exibir apenas um alerta</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="711"/> + <source>Nodes</source> + <translation>Nodes</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="717"/> + <source>Process monitor method</source> + <translation>Método de monitoramento de processo</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="751"/> + <source><html><head/><body><p>The default duration will take place when there's no UI connected.</p></body></html></source> + <translation><html><head/><body><p>A duração padrão ocorrerá quando não houver interface do usuário conectada.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="881"/> + <source><html><head/><body><p>Address of the node.</p><p>Default: unix:///tmp/osui.sock (unix:// is mandatory if it's a Unix socket)</p><p>It can also be an IP address with the port: 127.0.0.1:50051</p></body></html></source> + <translation><html><head/><body><p>Endereço do node</p><p>Padrão: unix:///tmp/osui.sock (unix:// é obrigatório se for um soquete Unix)</p><p>Também pode ser um endereço IP com a porta: 127.0.0.1:50051</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="884"/> + <source>Address</source> + <translation>Endereço</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1024"/> + <source>Default log level</source> + <translation>Nível de registro padrão</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="932"/> + <source>Version</source> + <translation>Versão</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="790"/> + <source><html><head/><body><p>The default action will take place when there's no UI connected.</p></body></html></source> + <translation><html><head/><body><p>A ação padrão ocorrerá quando não houver interface do usuário conectada.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="681"/> + <source>audit</source> + <translation type="obsolete">auditar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="734"/> + <source><html><head/><body><p>Log file to write logs.<br/></p><p>/dev/stdout will print logs to the standard output.</p></body></html></source> + <translation><html><head/><body><p>Arquivo de log para gravar logs.<br/></p><p>/dev/stdout irá imprimir registros na saída padrão.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="737"/> + <source>Log file</source> + <translation>Arquivo de log</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="788"/> + <source>IMPORTANT</source> + <translation type="obsolete">IMPORTANTE</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="793"/> + <source>WARNING</source> + <translation type="obsolete">ADVERTÊNCIA</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="798"/> + <source>ERROR</source> + <translation type="obsolete">ERRO</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="578"/> + <source><html><head/><body><p>If checked, opensnitch will prompt you to allow or deny connections that don't have an asocciated PID, due to several reasons.</p><p>The pop-up dialog will only contain information about the network connection.</p></body></html></source> + <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Se marcado, o opensnitch solicitará que você permita ou negue conexões que não tenham um PID associado, devido a vários motivos.&lt;/p&gt;&lt;p&gt;A caixa de diálogo pop-up conterá apenas informações sobre a conexão de rede.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="581"/> + <source>Intercept Unknown Connections</source> + <translation type="obsolete">Interceptar conexões desconhecidas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="809"/> + <source>HostName</source> + <translation>HostName</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="983"/> + <source>unix:///tmp/osui.sock</source> + <translation>unix:///tmp/osui.sock</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="868"/> + <source>until restart</source> + <translation>até reiniciar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="873"/> + <source>always</source> + <translation>sempre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="995"/> + <source>/var/log/opensnitchd.log</source> + <translation>/var/log/opensnitchd.log</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1000"/> + <source>/dev/stdout</source> + <translation>/dev/stdout</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="767"/> + <source>Apply configuration to all nodes</source> + <translation>Aplicar configuração a todos os nodes</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1039"/> + <source>Database</source> + <translation>Base de dados</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="630"/> + <source>Database name</source> + <translation type="obsolete">Nome do banco de dados</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1074"/> + <source>In memory</source> + <translation>Na memória</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1079"/> + <source>File</source> + <translation>Arquivo</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="669"/> + <source>/path/to/the/file.db</source> + <translation type="obsolete">/caminho/para/o/arquivo.db</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1345"/> + <source>Close</source> + <translation>Fechar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1356"/> + <source>Apply</source> + <translation>Aplicar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1367"/> + <source>Save</source> + <translation>Salvar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="235"/> + <source>until reboot</source> + <translation>até reiniciar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1093"/> + <source>Database type</source> + <translation>Tipo de banco de dados</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1100"/> + <source>Select</source> + <translation>Selecionar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="83"/> + <source>Configure the</source> + <translation type="obsolete">Configure o</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="83"/> + <source>Pop-ups default options</source> + <translation type="obsolete">Opções padrão de pop-ups</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="367"/> + <source>Pop-ups default position on screen</source> + <translation type="obsolete">Posição padrão dos pop-ups na tela</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="105"/> + <source><html><head/><body><p>The advanced view allows you to apply more filters on a connection</p><p>when a pop-up appears.</p></body></html></source> + <translation type="obsolete"><html><head/><body><p>A visualização avançada permite que você aplique mais filtros em uma conexão</p><p>quando um pop-up aparece.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="350"/> + <source>Show advanced view by default</source> + <translation>Mostrar visualização avançada por padrão</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="665"/> + <source>Action</source> + <translation>Ação</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="366"/> + <source><html><head/><body><p>If checked, the pop-ups will be displayed with the advanced view active.</p></body></html></source> + <translation><html><head/><body><p>Se marcado, os pop-ups serão exibidos com a visualização avançada ativa.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="334"/> + <source>Duration</source> + <translation>Duração</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="254"/> + <source><html><head/><body><p>By default when a new pop-up appears, in its simplest form, you'll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).</p><p>With these options, you can choose multiple fields to filter connections for.</p></body></html></source> + <translation><html><head/><body><p>Por padrão, quando um novo pop-up aparece, em sua forma mais simples, você será capaz de filtrar conexões ou aplicativos por uma propriedade da conexão (executável, porta, IP, etc).</p><p>Com essas opções, você pode escolher vários campos para filtrar conexões para.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="257"/> + <source>Filter connections also by:</source> + <translation>Filtre as conexões também por:</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="365"/> + <source>If checked, this field will be checked when a pop-up is displayed</source> + <translation type="obsolete">Se marcado, este campo será verificado quando um pop-up for exibido</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="81"/> + <source>User ID</source> + <translation>ID do usuário</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="97"/> + <source>Destination port</source> + <translation>Porta de destino</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="113"/> + <source>Destination IP</source> + <translation>IP de destino</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="451"/> + <source><html><head/><body><p>This timeout is the countdown you see when a pop-up dialog is shown.</p><p>If the pop-up is not answered, the default options will be applied.</p></body></html></source> + <translation><html><head/><body><p>Este tempo limite é a contagem regressiva que você vê quando uma caixa de diálogo pop-up é exibida.</p><p>Se o pop-up não for respondido, as opções padrão serão aplicadas.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="347"/> + <source>The advanced view allows you to easily select multiple fields to filter connections</source> + <translation>A visualização avançada permite que você selecione facilmente vários campos para filtrar conexões</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="110"/> + <source>If checked, this field will be selected when a pop-up is displayed</source> + <translation>Se marcado, este campo será selecionado quando um pop-up for exibido</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="150"/> + <source><html><head/><body><p>Pop-up default action.</p><p>When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.</p><p><br/></p><p>While a pop-up is asking the user to allow or deny a connection:</p><p>1. new outgoing connections are denied.</p><p>2. known connections are allowed or denied based on the rules defined by the user.</p></body></html></source> + <translation><html><head/><body><p>Ação padrão de pop-up.</p><p>Quando uma nova conexão de saída está prestes a ser estabelecida, esta ação será selecionada por padrão, então se o tempo limite disparar, esta é a opção que será aplicada.</p><p><br/></p><p>Enquanto um pop-up pede ao usuário para permitir ou negar uma conexão:</p><p>1. novas conexões de saída são negadas.</p><p>2. conexões conhecidas são permitidas ou negadas com base nas regras definidas pelo usuário.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="793"/> + <source>Default action when the GUI is disconnected</source> + <translation>Ação padrão quando a GUI é desconectada</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="894"/> + <source>Debug invalid connections</source> + <translation>Depurar conexões inválidas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="39"/> + <source>Pop-ups</source> + <translation>Pop-ups</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="64"/> + <source>Default options</source> + <translation>Opções padrão</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="321"/> + <source>Default position on screen</source> + <translation>Posição padrão na tela</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="479"/> + <source>any temporary rules</source> + <translation>quaisquer regras temporárias</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="492"/> + <source><html><head/><body><p>When this option is selected, the rules of the selected duration won't be added to the list of temporary rules in the GUI.</p><p><br/></p><p>Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</p></body></html></source> + <translation><html><head/><body><p>Quando esta opção é selecionada, as regras da duração selecionada não serão adicionadas à lista de regras temporárias na GUI.</p><p><br/></p><p>As regras temporárias ainda serão válidas e você pode usá-las quando solicitado a permitir/negar uma nova conexão.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="495"/> + <source>Don't save rules of duration</source> + <translation>Não salve regras de duração</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="601"/> + <source>Time</source> + <translation>Tempo</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="681"/> + <source>Destination</source> + <translation>Destino</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="649"/> + <source>Protocol</source> + <translation>Protocolo</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="697"/> + <source>Process</source> + <translation>Processo</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="617"/> + <source>Rule</source> + <translation>Regra</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="633"/> + <source>Node</source> + <translation>Node</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="723"/> + <source><html><head/><body><p>If checked, opensnitch will prompt you to allow or deny connections that don't have an asocciated PID, due to several reasons, mostly due to bad state connections.</p><p>The pop-up dialog will only contain information about the network connection.</p><p>There're some scenarios where these are valid connections though, like when establishing a VPN using wireguard.</p></body></html></source> + <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Se marcado, o opensnitch solicitará que você permita ou negue conexões que não tenham um PID asocciado, devido a vários motivos, principalmente devido a conexões de mau estado.&lt;/p&gt;&lt;p&gt;A caixa de diálogo pop-up conterá apenas informações sobre a conexão de rede.&lt;/p&gt;&lt;p&gt;Existem alguns cenários em que essas conexões são válidas, como ao estabelecer uma VPN usando wireguard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="589"/> + <source>Events tab columns</source> + <translation>Colunas da guia de eventos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="299"/> + <source>by PID</source> + <translation>por PID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="891"/> + <source><html><head/><body><p>If checked, OpenSnitch will prompt you to allow or deny connections that don't have an associated PID, due to several reasons, mostly due to bad state connections.</p><p>The pop-up dialog will only contain information about the network connection.</p><p>There're some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.</p></body></html></source> + <translation><html><head/><body><p>Se marcado, o OpenSnitch solicitará que você permita ou negue conexões que não tenham um PID associado, devido a vários motivos, principalmente devido a conexões ruins.</p><p>A caixa de diálogo pop-up conterá apenas informações sobre a conexão de rede.</p><p>Existem alguns cenários em que essas conexões são válidas, como ao estabelecer uma VPN usando o WireGuard.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="461"/> + <source>Disable pop-ups, only display an notification</source> + <translation>Desativar pop-ups, exibir apenas uma notificação</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="508"/> + <source>Desktop notifications</source> + <translation>Notificações da área de trabalho</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="526"/> + <source>Use system notifications</source> + <translation>Usar notificações do sistema</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="542"/> + <source>Use Qt notifications</source> + <translation>Usar notificações do Qt</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="571"/> + <source>Test</source> + <translation>Testar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1178"/> + <source>minutes</source> + <translation>minutos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1204"/> + <source>Minutes between events purges</source> + <translation>Minutos entre expurgos de eventos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1227"/> + <source>days</source> + <translation>dias</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1237"/> + <source>Maximum days of events to keep</source> + <translation>Máximo de dias de eventos para manter</translation> + </message> +</context> +<context> + <name>ProcessDetailsDialog</name> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="14"/> + <source>Process details</source> + <translation>Detalhes do processo</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="61"/> + <source>loading...</source> + <translation>carregando...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="81"/> + <source>CWD: loading...</source> + <translation>CWD: carregando...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="93"/> + <source>mem stats: loading...</source> + <translation>estatísticas mem: carregando...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="121"/> + <source>Status</source> + <translation>Estado</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="135"/> + <source>Open files</source> + <translation>Abrir arquivos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="149"/> + <source>I/O Statistics</source> + <translation>Estatísticas de I/O</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="163"/> + <source>Memory mapped files</source> + <translation>Arquivos mapeados na memória</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="177"/> + <source>Stack</source> + <translation>Pilha</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="191"/> + <source>Environment variables</source> + <translation>Variáveis de ambiente</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="210"/> + <source>Application pids</source> + <translation>Aplicação pids</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="240"/> + <source>Start or stop monitoring this process</source> + <translation>Inicie ou pare de monitorar este processo</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="256"/> + <source>Close</source> + <translation>Fechar</translation> + </message> +</context> +<context> + <name>RulesDialog</name> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="14"/> + <source>Rule</source> + <translation>Regra</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="118"/> + <source>Node</source> + <translation>Node</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="141"/> + <source>Apply rule to all nodes</source> + <translation>Aplicar regra a todos os nodes</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="239"/> + <source>From this command line</source> + <translation>Para esta linha de comando</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="206"/> + <source>From this executable</source> + <translation>Para este executável</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="751"/> + <source>Action</source> + <translation>Ação</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="138"/> + <source>/path/to/executable, .*/bin/executable[0-9\.]+$, ...</source> + <translation type="obsolete">/caminho/para/o/executavel, .*/bin/executable[0-9\.]+$, ...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="383"/> + <source>To this IP / Network</source> + <translation>Para este IP / Rede</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="792"/> + <source>once</source> + <translation>uma vez</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="797"/> + <source>30s</source> + <translation>30s</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="802"/> + <source>5m</source> + <translation>5m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="807"/> + <source>15m</source> + <translation>15m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="812"/> + <source>30m</source> + <translation>30m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="817"/> + <source>1h</source> + <translation>1h</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="230"/> + <source>until restart</source> + <translation type="obsolete">até reiniciar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="827"/> + <source>always</source> + <translation>sempre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="293"/> + <source>To this port</source> + <translation>Para esta porta</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="169"/> + <source>From this user ID</source> + <translation>Para este ID de usuário</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="419"/> + <source>Commas or spaces are not allowed to specify multiple domains. + +Use regular expressions instead: +.*(opensnitch|duckduckgo).com +.*\.google.com + +or a single domain: +www.gnu.org - it'll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ... +gnu.org - it'll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source> + <translation>Vírgulas ou espaços não podem especificar vários domínios. + +Em vez disso, use expressões regulares: +.*(opensnitch|duckduckgo).com +.*\.google.com + +ou um único domínio: +www.gnu.org - só vai filtrar www.gnu.org, não filtrará ftp.gnu.org, nem www2.gnu.org, ... +gnu.org - só vai filtrar gnu.org, não filtrará www.gnu.org, nem ftp.gnu.org, ...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="430"/> + <source>www.domain.org, .*\.domain.org</source> + <translation>www.dominio.org, .*\.dominio.org</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="323"/> + <source><html><head/><body><p>Only TCP, UDP or UDPLITE are allowed</p><p>You can use regexp, i.e.: ^(TCP|UDP)$</p></body></html></source> + <translation><html><head/><body><p>Apenas TCP, UDP ou UDPLITE são permitidos</p><p>Você pode usar expressão regulares, ou seja: ^(TCP|UDP)$</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="333"/> + <source>TCP</source> + <translation>TCP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="338"/> + <source>UDP</source> + <translation>UDP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="343"/> + <source>UDPLITE</source> + <translation>UDPLITE</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="348"/> + <source>TCP6</source> + <translation>TCP6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="353"/> + <source>UDP6</source> + <translation>UDP6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="358"/> + <source>UDPLITE6</source> + <translation>UDPLITE6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="440"/> + <source>You can specify a single IP: +- 192.168.1.1 + +or a regular expression: +- 192\.168\.1\.[0-9]+ + +multiple IPs: +- ^(192\.168\.1\.1|172\.16\.0\.1)$ + +You can also specify a subnet: +- 192.168.1.0/24 + +Note: Commas or spaces are not allowed to separate IPs or networks.</source> + <translation>Você pode especificar um único IP: +- 192.168.1.1 + +ou uma expressão regular: +- 192\.168\.1\.[0-9]+ + +vários IPs: +- ^(192\.168\.1\.1|172\.16\.0\.1)$ + +Você também pode especificar uma sub-rede: +- 192.168.1.0/24 + +Nota: Vírgulas ou espaços não são permitidos para separar IPs ou redes.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="459"/> + <source>LAN</source> + <translation>LAN</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="464"/> + <source>127.0.0.0/8</source> + <translation>127.0.0.0/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="469"/> + <source>192.168.0.0/24</source> + <translation>192.168.0.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="474"/> + <source>192.168.1.0/24</source> + <translation>192.168.1.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="479"/> + <source>192.168.2.0/24</source> + <translation>192.168.2.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="484"/> + <source>192.168.0.0/16</source> + <translation>192.168.0.0/16</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="489"/> + <source>169.254.0.0/16</source> + <translation>169.254.0.0/16</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="494"/> + <source>172.16.0.0/12</source> + <translation>172.16.0.0/12</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="499"/> + <source>10.0.0.0/8</source> + <translation>10.0.0.0/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="504"/> + <source>::1/128</source> + <translation>::1/128</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="509"/> + <source>fc00::/7</source> + <translation>fc00::/7</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="514"/> + <source>ff00::/8</source> + <translation>ff00::/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="519"/> + <source>fe80::/10</source> + <translation>fe80::/10</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="524"/> + <source>fd00::/8</source> + <translation>fd00::/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="784"/> + <source>Duration</source> + <translation>Duração</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="376"/> + <source>Protocol</source> + <translation>Protocolo</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="300"/> + <source>To this host</source> + <translation>Para este host</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="843"/> + <source>Deny</source> + <translation>Negar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="877"/> + <source>Allow</source> + <translation>Permitir</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="899"/> + <source>Name</source> + <translation>Nome</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="22"/> + <source>Enable</source> + <translation>Habilitar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="706"/> + <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them. + +000-allow-localhost +001-deny-broadcast +...</source> + <translation>As regras são verificadas em ordem alfabética, para que você possa nomeá-las de acordo para priorizá-las. + +000-permitir-localhost +001-negar-transmissão +...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="713"/> + <source>leave blank to autocreate</source> + <translation>deixe em branco para criar automaticamente</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="46"/> + <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one. + +You must name the rule in such manner that it'll be checked first, because they're checked in alphabetical order. For example: + +[x] Priority - 000-priority-rule +[ ] Priority - 001-less-priority-rule</source> + <translation>Se marcada, esta regra terá precedência sobre o resto das regras. Nenhuma outra regra será verificada após esta. + +Você deve nomear a regra de forma que ela seja verificada primeiro, porque eles são verificados em ordem alfabética. Por exemplo: + +[x] Prioridade - 000-regra-prioritaria +[ ] Prioridade - 001-regra-menos-prioritaria</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="54"/> + <source>Priority rule</source> + <translation>Regra de prioridade</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="74"/> + <source><html><head/><body><p>By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.<br/></p><p>If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.</p></body></html></source> + <translation><html><head/><body><p>Por padrão, o campo das regras não diferencia maiúsculas de minúsculas, ou seja, se um processo tentar acessar gOOgle.CoM e você tiver uma regra para Negar. *Google.com, a conexão será bloqueada.<br/></p><p>Se você marcar esta caixa, deverá especificar a string exata (domínio, executável, linha de comando) que deseja filtrar.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="77"/> + <source>Case-sensitive</source> + <translation>Sensível a maiúsculas e minúsculas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="369"/> + <source><html><head/><body><p>You can specify multiple ports using regular expressions:</p><p><br/></p><p>- 53, 80 or 443:</p><p>^(53|80|443)$</p><p><br/></p><p>- 53, 443 or 5551, 5552, 5553, etc:</p><p>^(53|443|555[0-9])$</p></body></html></source> + <translation><html><head/><body><p>Você pode especificar várias portas usando expressões regulares:</p><p><br/></p><p>- 53, 80 o 443:</p><p>^(53|80|443)$</p><p><br/></p><p>- 53, 443 o 5551, 5552, 5553, etc:</p><p>^(53|443|555[0-9])$</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="822"/> + <source>until reboot</source> + <translation>até reiniciar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="583"/> + <source>To this list of domains</source> + <translation>Para esta lista de domínios</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="539"/> + <source><html><head/><body><p>Select a directory with lists of domains to block or allow.</p><p>Put inside that directory files with any extension containing lists of domains.</p><p><br/>The format of each entry of a list is as follow (hosts format):</p><p>127.0.0.1 www.domain.com</p><p>or </p><p>0.0.0.0 www.domain.com</p></body></html></source> + <translation type="obsolete"><html><head/><body><p>Selecione um diretório com listas de domínios para bloquear ou permitir.</p><p>Coloque dentro desse diretório arquivos com qualquer extensão que contenha listas de domínios.</p><p><br/>O formato de cada entrada de uma lista é o seguinte (formato de hosts):</p><p>127.0.0.1 www.dominio.com</p><p>ou </p><p>0.0.0.0 www.dominio.com</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="163"/> + <source>Applications</source> + <translation>Aplicativos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="216"/> + <source><html><head/><body><p>This field will only match the executable path. It is not modifiable by the user.<br/></p><p>You can use regular expressions to deny executions from /tmp for example:<br/></p><p>^/tmp/.*$</p></body></html></source> + <translation><html><head/><body><p>Este campo irá corresponder apenas ao caminho do executável. Não é modificável pelo usuário.<br/></p><p>Você pode usar expressões regulares para negar execuções de /tmp, por exemplo:<br/></p><p>^/tmp/.*$</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="229"/> + <source><html><head/><body><p>This field will contain and match the command line that was executed by the user.<br/></p><p>If the user typed the command, only the command will appear:</p><p>telnet 1.2.3.4<br/></p><p>If the user typed the absolute or relative path to the command, that is what will appear:</p><p>/usr/bin/telnet 1.2.3.4</p><p>../../../usr/bin/telnet 1.2.3.4</p></body></html></source> + <translation><html><head/><body><p>Este campo irá conter e corresponder à linha de comando que foi executada pelo usuário.<br/></p><p>Se o usuário digitou o comando, apenas o comando aparecerá:</p><p>telnet 1.2.3.4<br/></p><p>Se o usuário digitou o caminho absoluto ou relativo para o comando, é isso que aparecerá:</p><p>/usr/bin/telnet 1.2.3.4</p><p>../../../usr/bin/telnet 1.2.3.4</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="246"/> + <source>From this PID</source> + <translation>A partir deste PID</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="287"/> + <source>Network</source> + <translation>Rede</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="536"/> + <source>List of domains/IPs</source> + <translation>Lista de domínios/IPs</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="542"/> + <source>To this list of network ranges</source> + <translation>Para esta lista de intervalos de rede</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="549"/> + <source>To this list of IPs</source> + <translation>Para esta lista de IPs</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="574"/> + <source><html><head/><body><p>Select a directory with files containing list of IPs to block or allow:</p><p>1.2.3.4.5</p><p>1.2.3.4.6</p><p>.</p><p>etc.</p><p>One IP per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation><html><head/><body><p>Selecione um diretório com arquivos contendo uma lista de IPs para bloquear ou permitir:</p><p>1.2.3.4.5</p><p>1.2.3.4.6</p><p>.</p><p>etc.</p><p>Um IP por linha. Linhas vazias ou iniciadas com # são ignoradas.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="608"/> + <source><html><head/><body><p>Select a directory with files containing list of network ranges to block or allow:</p><p>1.2.3.0/24</p><p>80.34.56.0/20</p><p>.</p><p>etc.<br/></p><p>One Network Range per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation><html><head/><body><p>Selecione um diretório com arquivos contendo uma lista de intervalos de rede para bloquear ou permitir:</p><p>1.2.3.0/24</p><p>80.34.56.0/20</p><p>.</p><p>etc.<br/></p><p>Um intervalo de rede por linha. Linhas vazias ou iniciadas com # são ignoradas.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="636"/> + <source><html><head/><body><p>Select a directory with lists of domains to block or allow.</p><p>Put inside that directory files with any extension containing lists of domains.</p><p><br/>The format of each entry of a list is as follow (hosts format):</p><p>127.0.0.1 www.domain.com</p><p>or </p><p>0.0.0.0 www.domain.com</p><p>Empty lines or started with # are ignored.</p></body></html></source> + <translation><html><head/><body><p>Selecione um diretório com listas de domínios para bloquear ou permitir.</p><p>Coloque dentro desse diretório arquivos com qualquer extensão contendo listas de domínios.</p><p><br/>O formato de cada entrada de uma lista é o seguinte (formato de hosts):</p><p>127.0.0.1 www.domain.com</p><p>or </p><p>0.0.0.0 www.domain.com</p><p>Linhas vazias ou iniciadas com # são ignoradas.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="651"/> + <source>To this list of domains +(regular expressions)</source> + <translation>Para esta lista de domínios +(expressões regulares)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="677"/> + <source><html><head/><body><p>Select a directory with files containing regular expressions of domains to block or allow:</p><p>.*\.example\.com</p><p>You can also use a domain as is: &quot;example.com&quot; , and it'll match whatever.example.com, whatever.example.com.localdomain, etc.</p><p>One domain per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation><html><head/><body><p>Selecione um diretório com arquivos contendo expressões regulares de domínios para bloquear ou permitir:</p><p>.*\.example\.com</p><p>Você também pode usar um domínio como: &quot;example.com&quot; , e vai combinar whatever.example.com, whatever.example.com.localdomain, etc.</p><p>Um domínio por linha. Linhas vazias ou iniciadas com # são ignoradas.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="857"/> + <source>Reject</source> + <translation>Rejeitar</translation> + </message> +</context> +<context> + <name>StatsDialog</name> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="34"/> + <source>OpenSnitch Network Statistics</source> + <translation>Estatísticas da rede OpenSnitch</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="290"/> + <source>Save to CSV.</source> + <translation>Salvar em CSV.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="300"/> + <source>Ctrl+S</source> + <translation>Ctrl+S</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="351"/> + <source>Create a new rule</source> + <translation>Crie uma nova regra</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="381"/> + <source><html><head/><body><p><span style=" font-size:11pt; font-weight:600;">hostname - 192.168.1.1</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-size:11pt; font-weight:600;">hostname - 192.168.1.1</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="420"/> + <source>Status</source> + <translation>Estado</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1668"/> + <source>-</source> + <translation>-</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="464"/> + <source>Start or Stop interception</source> + <translation>Iniciar ou parar a interceptação</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="509"/> + <source>Events</source> + <translation>Eventos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="94"/> + <source>Filter</source> + <translation>Filtro</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="107"/> + <source>Allow</source> + <translation>Permitir</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="116"/> + <source>Deny</source> + <translation>Negar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="143"/> + <source>Ex.: firefox</source> + <translation>Ex.: firefox</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="199"/> + <source>50</source> + <translation>50</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="204"/> + <source>100</source> + <translation>100</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="209"/> + <source>200</source> + <translation>200</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="214"/> + <source>300</source> + <translation>300</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="751"/> + <source>Nodes</source> + <translation>Nodes</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="554"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on the Addr column to view details of a node)</span></p></body></html></source> + <translation type="obsolete"><html><head/><body><p><span style=" font-size:7pt;">(clique duas vezes na coluna endereço para ver os detalhes de um node)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1572"/> + <source>Rules</source> + <translation>Regras</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="860"/> + <source>enable</source> + <translation>habilitar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="674"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on the Name column to view details of a rule)</span></p></body></html></source> + <translation type="obsolete"><html><head/><body><p><span style=" font-size:7pt;">(clique duas vezes na coluna Nome para ver os detalhes de uma regra)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="692"/> + <source>search rule name</source> + <translation type="obsolete">nome da regra de pesquisa</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="707"/> + <source>Application rules</source> + <translation>Regras de aplicação</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="809"/> + <source>Permanent</source> + <translation>Permanente</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="818"/> + <source>Temporary</source> + <translation>Temporário</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="936"/> + <source>Hosts</source> + <translation>Hosts</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1364"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click to view details of an item)</span></p></body></html></source> + <translation type="obsolete"><html><head/><body><p><span style=" font-size:7pt;">(clique duas vezes para ver os detalhes de um item)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1023"/> + <source>Applications</source> + <translation>Aplicativos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1130"/> + <source>Addresses</source> + <translation>Endereços</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1217"/> + <source>Ports</source> + <translation>Portas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1301"/> + <source>Users</source> + <translation>Usuários</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1407"/> + <source>Connections</source> + <translation>Conexões</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1462"/> + <source>Dropped</source> + <translation>Dropado</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1517"/> + <source>Uptime</source> + <translation>Tempo de atividade</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1642"/> + <source>Version</source> + <translation>Versão</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="227"/> + <source>Delete all intercepted events</source> + <translation>Excluir todos os eventos interceptados</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="867"/> + <source>Edit rule</source> + <translation>Editar regra</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="881"/> + <source>Delete rule</source> + <translation>Excluir regra</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="926"/> + <source>Delete all intercepted hosts</source> + <translation type="obsolete">Excluir todos os hosts interceptadas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1051"/> + <source>Delete all intercepted applications</source> + <translation type="obsolete">Excluir todos os aplicativos interceptadas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1159"/> + <source>Delete all intercepted addresses</source> + <translation type="obsolete">Excluir todos os endereços interceptados</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1261"/> + <source>Delete all intercepted ports</source> + <translation type="obsolete">Excluir todas as portas interceptadas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1371"/> + <source>Delete all intercepted users</source> + <translation type="obsolete">Excluir todos os usuários interceptados</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="699"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on a row to view details of a rule)</span></p></body></html></source> + <translation type="obsolete"><html><head/><body><p><span style=" font-size:7pt;">(clique duas vezes em uma linha para ver os detalhes de uma regra)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="915"/> + <source>Delete connections that matched this rule</source> + <translation>Exclua conexões que correspondam a esta regra</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="800"/> + <source>All applications</source> + <translation>Todos os aplicativos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="125"/> + <source>Reject</source> + <translation>Rejeitar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="177"/> + <source>0</source> + <translation>0</translation> + </message> +</context> +<context> + <name>contextual_menu</name> + <message> + <location filename="../../../opensnitch/service.py" line="43"/> + <source>Statistics</source> + <translation>Estatísticas</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="46"/> + <source>Help</source> + <translation>Ajuda</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="47"/> + <source>Close</source> + <translation>Fechar</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="44"/> + <source>Enable</source> + <translation>Habilitar</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="45"/> + <source>Disable</source> + <translation>Desabilitar</translation> + </message> +</context> +<context> + <name>notifications</name> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="518"/> + <source>System notifications are not available, you need to install python3-notify2.</source> + <translation>As notificações do sistema não estão disponíveis, você precisa instalar o python3-notify2.</translation> + </message> +</context> +<context> + <name>popups</name> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="52"/> + <source>until reboot</source> + <translation>até reiniciar</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="54"/> + <source>forever</source> + <translation>para sempre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="91"/> + <source>Allow</source> + <translation>Permitir</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="92"/> + <source>Deny</source> + <translation>Negar</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="208"/> + <source>Unknown process</source> + <translation type="obsolete">Processo desconhecido</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="285"/> + <source>Outgoing connection</source> + <translation>Conexão de saída</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="290"/> + <source>Process launched from:</source> + <translation>Processo lançado de:</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="321"/> + <source>from this executable</source> + <translation>a partir deste executável</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="325"/> + <source>from this command line</source> + <translation>a partir desta linha de comando</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="327"/> + <source>to port {0}</source> + <translation>para a porta {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="390"/> + <source>to {0}</source> + <translation>para {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="330"/> + <source>from user {0}</source> + <translation>do usuário {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="347"/> + <source>to {0}.*</source> + <translation>para {0}.*</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="400"/> + <source>to *.{0}</source> + <translation>para *.{0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="378"/> + <source>to *{0}</source> + <translation type="obsolete">para *{0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="434"/> + <source><b>Remote</b> process %s running on <b>%s</b></source> + <translation><b>Processo remoto</b> %s rodando em <b>%s</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="438"/> + <source>is connecting to <b>%s</b> on %s port %d</source> + <translation>está conectando a <b>%s</b> em %s na porta %d</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="444"/> + <source>is attempting to resolve <b>%s</b> via %s, %s port %d</source> + <translation>está tentando resolver <b>%s</b> via %s, %s porta %d</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="334"/> + <source>from this PID</source> + <translation>a partir desse PID</translation> + </message> + <message> + <location filename="../../../opensnitch/notifications.py" line="108"/> + <source>New outgoing connection</source> + <translation>Nova conexão de saída</translation> + </message> +</context> +<context> + <name>preferences</name> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="208"/> + <source>Server address can not be empty</source> + <translation>O endereço do servidor não pode estar vazio</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="448"/> + <source>Configuration applied.</source> + <translation>Configuração aplicada.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="299"/> + <source>Exception saving config: {0}</source> + <translation>Configuração de salvamento de exceção: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="389"/> + <source>Applying configuration on {0} ...</source> + <translation>Aplicando configuração em {0} ...</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="238"/> + <source>Error loading {0} configuration</source> + <translation>Erro ao carregar configuração de {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="450"/> + <source>Error applying configuration: {0}</source> + <translation>Erro ao aplicar configuração: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="323"/> + <source>Warning</source> + <translation>Aviso</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="323"/> + <source>You must select a file for the database<br>or choose "In memory" type.</source> + <translation>Você deve selecionar um arquivo para o banco de dados&lt;br&gt;ou escolher o tipo &quot;Na memória&quot;.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="329"/> + <source>DB type changed</source> + <translation>Tipo de banco de dados alterado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="329"/> + <source>Restart the GUI in order effects to take effect</source> + <translation>Reinicie a GUI para que os efeitos tenham efeito</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="480"/> + <source>Hover the mouse over the texts to display the help<br><br>Don't forget to visit the wiki: <a href="{0}">{0}</a></source> + <translation>Passe o mouse sobre os textos para exibir a ajuda&lt;br&gt;&lt;br&gt;Não se esqueça de visitar a wiki: &lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</translation> + </message> +</context> +<context> + <name>proc_details</name> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="97"/> + <source><b>Error loading process information:</b> <br><br> + +</source> + <translation><b>Erro ao carregar as informações do processo:</b> <br><br> + +</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="116"/> + <source><b>Error stopping monitoring process:</b><br><br></source> + <translation><b>Erro ao parar o processo de monitoramento:</b><br><br></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="156"/> + <source>loading...</source> + <translation>carregando...</translation> + </message> +</context> +<context> + <name>rules</name> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="162"/> + <source>There're no nodes connected.</source> + <translation>Não há nodes conectados.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="203"/> + <source>Rule applied.</source> + <translation>Regra aplicada.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="527"/> + <source>protocol can not be empty, or uncheck it</source> + <translation>protocolo não pode estar vazio ou desmarque-o</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="541"/> + <source>Protocol regexp error</source> + <translation>Erro de expressão de protocolo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="545"/> + <source>process path can not be empty</source> + <translation>o caminho do processo não pode estar vazio</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="559"/> + <source>Process path regexp error</source> + <translation>Erro de expressão regular do caminho do processo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="563"/> + <source>command line can not be empty</source> + <translation>a linha de comando não pode estar vazia</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="577"/> + <source>Command line regexp error</source> + <translation>Erro de expressão regular da linha de comando</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="581"/> + <source>Dest port can not be empty</source> + <translation>A porta de destino não pode estar vazia</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="595"/> + <source>Dst port regexp error</source> + <translation>Erro de expressão regular da porta Dst</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="599"/> + <source>Dest host can not be empty</source> + <translation>Dest host não pode estar vazio</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="613"/> + <source>Dst host regexp error</source> + <translation>Erro de expressão regular do host Dst</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="617"/> + <source>Dest IP/Network can not be empty</source> + <translation>O IP/rede de destino não pode estar vazio</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="639"/> + <source>Dst IP regexp error</source> + <translation>Erro de expressão regular de IP Dst</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="651"/> + <source>User ID can not be empty</source> + <translation>O ID do usuário não pode estar vazio</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="665"/> + <source>User ID regexp error</source> + <translation>Erro de expressão regular do ID do usuário</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="205"/> + <source>Error applying rule: {0}</source> + <translation>Erro ao aplicar regra: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="777"/> + <source><b>Rule not supported</b></source> + <translation><b>Regra não suportada</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="739"/> + <source>Lists field cannot be empty</source> + <translation>O campo de listas não pode estar vazio</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="741"/> + <source>Lists field must be a directory</source> + <translation>O campo de listas deve ser um diretório</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="429"/> + <source><b>Error loading rule</b></source> + <translation><b>Erro ao carregar regra</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="179"/> + <source>There's already a rule with this name.</source> + <translation>Já existe uma regra com este nome.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="669"/> + <source>PID field can not be empty</source> + <translation>O campo PID não pode ficar vazio</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="683"/> + <source>PID field regexp error</source> + <translation>Erro de expressão regular do campo PID</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="764"/> + <source>Select at least one field.</source> + <translation>Selecione pelo menos um campo.</translation> + </message> +</context> +<context> + <name>stats</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="306"/> + <source>Not running</source> + <translation>Desativado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="307"/> + <source>Disabled</source> + <translation>Desativado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="308"/> + <source>Running</source> + <translation>Ativo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="886"/> + <source> Your are about to delete this rule. </source> + <translation> Você está prestes a excluir esta regra. </translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1258"/> + <source> Are you sure?</source> + <translation> Você tem certeza?</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="572"/> + <source>OpenSnitch Network Statistics {0}</source> + <translation>Estatísticas da rede OpenSnitch {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="574"/> + <source>OpenSnitch Network Statistics for {0}</source> + <translation>Estatísticas da rede OpenSnitch para {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <translation type="obsolete">Nome</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="294"/> + <source>Address</source> + <translation type="obsolete">Endereço</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="176"/> + <source>Status</source> + <translation type="obsolete">Estado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="183"/> + <source>Version</source> + <translation type="obsolete">Versão</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="180"/> + <source>Rules</source> + <translation type="obsolete">Regras</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Time</source> + <translation type="obsolete">Tempo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Action</source> + <translation type="obsolete">Ação</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Duration</source> + <translation type="obsolete">Duração</translation> + </message> + <message> + <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="24"/> + <source>Hits</source> + <translation>Acertos</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>Protocol</source> + <translation type="obsolete">Protocolo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1850"/> + <source>Save as CSV</source> + <translation>Salvar como CSV</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>Enabled</source> + <translation type="obsolete">Ativado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="752"/> + <source>Delete</source> + <translation>Deletar</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="580"/> + <source><b>Error:</b><br><br>{0}</source> + <translation type="obsolete">&lt;b&gt;Erro:&lt;/b&gt;&lt;br&gt;&lt;br&gt;{0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="745"/> + <source>Disable</source> + <translation>Desabilitar</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="747"/> + <source>Enable</source> + <translation>Habilitar</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="750"/> + <source>Duplicate</source> + <translation>Duplicado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="751"/> + <source>Edit</source> + <translation>Editar</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="905"/> + <source>Rule not found by that name and node</source> + <translation>Regra não encontrada por esse nome e node</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="935"/> + <source><b>Error:</b><br><br></source> + <comment>{0}</comment> + <translation><b>Erro:</b><br><br></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="942"/> + <source>Warning:</source> + <translation>Atenção:</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="731"/> + <source>Allow</source> + <translation>Permitir</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="732"/> + <source>Deny</source> + <translation>Negar</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="736"/> + <source>Always</source> + <translation>Sempre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="737"/> + <source>Until reboot</source> + <translation>Até reiniciar</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1258"/> + <source> You are about to delete this rule. </source> + <translation> Você está prestes a excluir esta regra. </translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="308"/> + <source>Rule</source> + <translation type="obsolete">Regra</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <comment>xxxxx</comment> + <translation type="obsolete">Nome</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Nome</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="294"/> + <source>Address</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Endereço</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="295"/> + <source>Status</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Estado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="297"/> + <source>Version</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Versão</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="298"/> + <source>Rules</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Regras</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Time</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Tempo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Action</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Ação</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Duration</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Duração</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>Enabled</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Ativado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="304"/> + <source>Hits</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Acertos</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>Protocol</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Protocolo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="308"/> + <source>Rule</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Regra</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="281"/> + <source>Name</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Nome</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="282"/> + <source>Address</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Endereço</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="283"/> + <source>Status</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Estado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="284"/> + <source>Hostname</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Hostname</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="382"/> + <source>Version</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Versão</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="379"/> + <source>Rules</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Regras</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="288"/> + <source>Time</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Tempo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="289"/> + <source>Action</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Ação</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="290"/> + <source>Duration</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Duração</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="291"/> + <source>Node</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Node</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="292"/> + <source>Enabled</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Habilitado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="401"/> + <source>Hits</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Acessos</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="295"/> + <source>Protocol</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Protocolo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="296"/> + <source>Process</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Processo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="298"/> + <source>Destination</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Destino</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Rule</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Regra</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>UserID</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>UserID</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="304"/> + <source>LastConnection</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>UltimaConexao</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="297"/> + <source>Args</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Args</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>DstIP</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>DstIP</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>DstHost</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>DstHost</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>DstPort</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>TempoAtividade</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="179"/> + <source>Uptime</source> + <translation type="obsolete">Tempo de atividade</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="181"/> + <source>Connections</source> + <translation type="obsolete">Conexões</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="182"/> + <source>Dropped</source> + <translation type="obsolete">Dropado</translation> + </message> + <message> + <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="23"/> + <source>What</source> + <translation>Qual</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="733"/> + <source>Reject</source> + <translation>Rejeitar</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="723"/> + <source>Apply to</source> + <translation>Aplicar para</translation> + </message> + <message> + <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="25"/> + <source>Network name</source> + <translation>Nome da rede</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="285"/> + <source>Uptime</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Tempo de atividade</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="380"/> + <source>Connections</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Conexoes</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="381"/> + <source>Dropped</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Dropado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="400"/> + <source>What</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Qual</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Precedence</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Precedencia</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="644"/> + <source>New node connected</source> + <translation>Novo node conectado</translation> + </message> +</context> +</TS> diff --git a/ui/i18n/locales/ro_RO/opensnitch-ro_RO.ts b/ui/i18n/locales/ro_RO/opensnitch-ro_RO.ts new file mode 100644 index 0000000..1f77a40 --- /dev/null +++ b/ui/i18n/locales/ro_RO/opensnitch-ro_RO.ts @@ -0,0 +1,1732 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.0" language="ro_RO"> +<context> + <name>Dialog</name> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="34"/> + <source>opensnitch-qt</source> + <translation>opensnitch-qt</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="679"/> + <source>from this executable</source> + <translation>din acest executabil</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="684"/> + <source>from this command line</source> + <translation>din această linie de comandă</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="689"/> + <source>this destination port</source> + <translation>acest port de destinație</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="694"/> + <source>this user</source> + <translation>acest utilizator</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="699"/> + <source>this destination ip</source> + <translation>această adresă IP de destinație</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="842"/> + <source>+</source> + <translation>+</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="723"/> + <source>once</source> + <translation>o dată</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="728"/> + <source>30s</source> + <translation>30s</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="733"/> + <source>5m</source> + <translation>5m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="738"/> + <source>15m</source> + <translation>15m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="743"/> + <source>30m</source> + <translation>30m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="748"/> + <source>1h</source> + <translation>1o</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="753"/> + <source>until reboot</source> + <translation>până la repornire</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="758"/> + <source>forever</source> + <translation>mereu</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="784"/> + <source>Deny</source> + <translation>Refuză</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="813"/> + <source>Allow</source> + <translation>Permite</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="299"/> + <source>User ID</source> + <translation>ID utilizator</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="333"/> + <source><html><head/><body><p><span style=" font-weight:600;">Executed from</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-weight:600;">Executat din</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="630"/> + <source>TextLabel</source> + <translation>EtichetăText</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="426"/> + <source>Source IP</source> + <translation>Adresă IP sursă</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="449"/> + <source>Process ID</source> + <translation>ID proces</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="582"/> + <source>Destination IP</source> + <translation>Adresă IP destinație</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="605"/> + <source>Dst Port</source> + <translation>Port destinație</translation> + </message> +</context> +<context> + <name>PreferencesDialog</name> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="14"/> + <source>Preferences</source> + <translation>Preferințe</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="417"/> + <source>UI</source> + <translation>Interfață utilizator</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="361"/> + <source>Show advanced view by default</source> + <translation>Arată implicit vizualizarea avansată</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="695"/> + <source>once</source> + <translation>o dată</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="226"/> + <source>30s</source> + <translation>30s</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="231"/> + <source>5m</source> + <translation>5m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="236"/> + <source>15m</source> + <translation>15m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="241"/> + <source>30m</source> + <translation>30m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="246"/> + <source>1h</source> + <translation>1o</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="251"/> + <source>until reboot</source> + <translation>până la repornire</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="256"/> + <source>forever</source> + <translation>mereu</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="526"/> + <source>Action</source> + <translation>Acțiune</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="325"/> + <source>Default target</source> + <translation>Țintă implicită</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="377"/> + <source><html><head/><body><p>If checked, the pop-ups will be displayed with the advanced view active.</p></body></html></source> + <translation><html><head/><body><p>Dacă este bifată, ferestrele de notificare care apar vor fi afișate cu vizualizarea avansată activă.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="737"/> + <source>deny</source> + <translation>refuză</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="746"/> + <source>allow</source> + <translation>permite</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="290"/> + <source>by executable</source> + <translation>după executabil</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="295"/> + <source>by command line</source> + <translation>după linia de comandă</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="300"/> + <source>by destination port</source> + <translation>după portul de destinație</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="305"/> + <source>by destination ip</source> + <translation>după adresa IP de destinație</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="310"/> + <source>by user id</source> + <translation>după identificatorul utilizatorului</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="186"/> + <source>center</source> + <translation>centru</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="191"/> + <source>top right</source> + <translation>sus la dreapta</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="196"/> + <source>bottom right</source> + <translation>jos la dreapta</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="201"/> + <source>top left</source> + <translation>sus la stânga</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="206"/> + <source>bottom left</source> + <translation>jos la stânga</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="342"/> + <source>Pop-up default duration</source> + <translation>Durată implicită fereastră de notificare</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="345"/> + <source>Duration</source> + <translation>Durată</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="270"/> + <source><html><head/><body><p>By default when a new pop-up appears, in its simplest form, you'll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).</p><p>With these options, you can choose multiple fields to filter connections for.</p></body></html></source> + <translation><html><head/><body><p>By default when a new pop-up appears, in its simplest form, you'll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).</p><p>With these options, you can choose multiple fields to filter connections for.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="273"/> + <source>Filter connections also by:</source> + <translation>Filtrează conexiunile și după:</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="97"/> + <source>User ID</source> + <translation>ID utilizator</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="113"/> + <source>Destination port</source> + <translation>Port destinație</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="129"/> + <source>Destination IP</source> + <translation>Adresă IP destinație</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="406"/> + <source>Disable pop-ups, only display an alert</source> + <translation>Dezactivează ferestrele de notificare, arată doar o alertă</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="396"/> + <source><html><head/><body><p>This timeout is the countdown you see when a pop-up dialog is shown.</p><p>If the pop-up is not answered, the default options will be applied.</p></body></html></source> + <translation><html><head/><body><p>This timeout is the countdown you see when a pop-up dialog is shown.</p><p>If the pop-up is not answered, the default options will be applied.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="399"/> + <source>Default timeout</source> + <translation>Durată implicită pentru alegere</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="540"/> + <source>Nodes</source> + <translation>Noduri</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="546"/> + <source>Process monitor method</source> + <translation>Metodă monitorizare procese</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="563"/> + <source><html><head/><body><p>Log file to write logs.<br/></p><p>/dev/stdout will print logs to the standard output.</p></body></html></source> + <translation><html><head/><body><p>Fișierul jurnal unde să se scrie jurnalizări.<br/></p><p>/dev/stdout va tipări jurnalizările pe ieșirea standard.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="566"/> + <source>Log file</source> + <translation>Fișier de jurnalizare</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="580"/> + <source><html><head/><body><p>The default duration will take place when there's no UI connected.</p></body></html></source> + <translation><html><head/><body><p>Durata implicită va fi folosită când nu este conectată nicio interfață grafică.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="583"/> + <source>Default duration</source> + <translation>Durată implicită</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="596"/> + <source>Apply configuration to all nodes</source> + <translation>Aplică configurația la toate nodurile</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="619"/> + <source><html><head/><body><p>The default action will take place when there's no UI connected.</p></body></html></source> + <translation><html><head/><body><p>Acțiunea implicită va fi folosită când nu este conectată nicio interfață grafică.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="638"/> + <source>HostName</source> + <translation>NumeGazdă</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="700"/> + <source>until restart</source> + <translation>până la repornire</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="705"/> + <source>always</source> + <translation>întotdeauna</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="713"/> + <source><html><head/><body><p>Address of the node.</p><p>Default: unix:///tmp/osui.sock (unix:// is mandatory if it's a Unix socket)</p><p>It can also be an IP address with the port: 127.0.0.1:50051</p></body></html></source> + <translation><html><head/><body><p>Address of the node.</p><p>Default: unix:///tmp/osui.sock (unix:// is mandatory if it's a Unix socket)</p><p>It can also be an IP address with the port: 127.0.0.1:50051</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="716"/> + <source>Address</source> + <translation>Adresă</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="764"/> + <source>Version</source> + <translation>Versiune</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="815"/> + <source>unix:///tmp/osui.sock</source> + <translation>unix:///tmp/osui.sock</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="827"/> + <source>/var/log/opensnitchd.log</source> + <translation>/var/log/opensnitchd.log</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="832"/> + <source>/dev/stdout</source> + <translation>/dev/stdout</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="856"/> + <source>Default log level</source> + <translation>Nivel implicit de jurnalizare</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="871"/> + <source>Database</source> + <translation>Bază de date</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="918"/> + <source>Database type</source> + <translation>Tip bază de date</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="925"/> + <source>Select</source> + <translation>Selectare</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="946"/> + <source>In memory</source> + <translation>În memorie</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="951"/> + <source>File</source> + <translation>Fișier</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1008"/> + <source>Close</source> + <translation>Închide</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1019"/> + <source>Apply</source> + <translation>Aplică</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1030"/> + <source>Save</source> + <translation>Salvează</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="358"/> + <source>The advanced view allows you to easily select multiple fields to filter connections</source> + <translation>Vizualizarea avansată vă permite să selectați cu ușurință câmpuri multiple pentru a filtra conexiunile</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="126"/> + <source>If checked, this field will be selected when a pop-up is displayed</source> + <translation>Dacă este bifată, acest câmp va fi selectat când o fereastră de notificare este afișată</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="166"/> + <source><html><head/><body><p>Pop-up default action.</p><p>When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.</p><p><br/></p><p>While a pop-up is asking the user to allow or deny a connection:</p><p>1. new outgoing connections are denied.</p><p>2. known connections are allowed or denied based on the rules defined by the user.</p></body></html></source> + <translation><html><head/><body><p>Pop-up default action.</p><p>When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.</p><p><br/></p><p>While a pop-up is asking the user to allow or deny a connection:</p><p>1. new outgoing connections are denied.</p><p>2. known connections are allowed or denied based on the rules defined by the user.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="622"/> + <source>Default action when the GUI is disconnected</source> + <translation>Acțiune implicită când interfața grafică cu utilizatorul este deconectată</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="726"/> + <source>Debug invalid connections</source> + <translation>Depanează conexiunile nevalide</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="39"/> + <source>Pop-ups</source> + <translation>Ferestre de notificare</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="80"/> + <source>Default options</source> + <translation>Opțiuni implicite</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="332"/> + <source>Default position on screen</source> + <translation>Compozziția implicită pe ecran</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="437"/> + <source>any temporary rules</source> + <translation>oricare regulă temporară</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="450"/> + <source><html><head/><body><p>When this option is selected, the rules of the selected duration won't be added to the list of temporary rules in the GUI.</p><p><br/></p><p>Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</p></body></html></source> + <translation><html><head/><body><p>When this option is selected, the rules of the selected duration won't be added to the list of temporary rules in the GUI.</p><p><br/></p><p>Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="453"/> + <source>Don't save rules of duration</source> + <translation>Nu salva regulile de durată</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="466"/> + <source>Time</source> + <translation>Timp</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="476"/> + <source>Destination</source> + <translation>Destinație</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="486"/> + <source>Protocol</source> + <translation>Protocol</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="496"/> + <source>Process</source> + <translation>Proces</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="506"/> + <source>Rule</source> + <translation>Regulă</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="516"/> + <source>Node</source> + <translation>Nod</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="723"/> + <source><html><head/><body><p>If checked, opensnitch will prompt you to allow or deny connections that don't have an asocciated PID, due to several reasons, mostly due to bad state connections.</p><p>The pop-up dialog will only contain information about the network connection.</p><p>There're some scenarios where these are valid connections though, like when establishing a VPN using wireguard.</p></body></html></source> + <translation><html><head/><body><p>If checked, opensnitch will prompt you to allow or deny connections that don't have an asocciated PID, due to several reasons, mostly due to bad state connections.</p><p>The pop-up dialog will only contain information about the network connection.</p><p>There're some scenarios where these are valid connections though, like when establishing a VPN using wireguard.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="460"/> + <source>Events tab columns</source> + <translation>Coloane etichete evenimente</translation> + </message> +</context> +<context> + <name>ProcessDetailsDialog</name> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="14"/> + <source>Process details</source> + <translation>Detalii proces</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="61"/> + <source>loading...</source> + <translation>Se încarcă...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="81"/> + <source>CWD: loading...</source> + <translation>CWD: loading...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="93"/> + <source>mem stats: loading...</source> + <translation>Statistici memorie: se încarcă...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="121"/> + <source>Status</source> + <translation>Stare</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="135"/> + <source>Open files</source> + <translation>Deschidere fișiere</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="149"/> + <source>I/O Statistics</source> + <translation>Statistici intrare/ieșire</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="163"/> + <source>Memory mapped files</source> + <translation>Fișiere cartografiate în memorie</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="177"/> + <source>Stack</source> + <translation>Stivă</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="191"/> + <source>Environment variables</source> + <translation>Variabile de mediu</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="210"/> + <source>Application pids</source> + <translation>Identificatori procese aplicație</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="240"/> + <source>Start or stop monitoring this process</source> + <translation>Pornește sau oprește monitorizarea acestui proces</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="256"/> + <source>Close</source> + <translation>Închide</translation> + </message> +</context> +<context> + <name>RulesDialog</name> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="14"/> + <source>Rule</source> + <translation>Regulă</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="22"/> + <source>Node</source> + <translation>Nod</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="45"/> + <source>Apply rule to all nodes</source> + <translation>Aplică regula la toate nodurile</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="115"/> + <source>To this IP / Network</source> + <translation>Pentru această adresă IP / rețea</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="138"/> + <source>/path/to/executable, .*/bin/executable[0-9\.]+$, ...</source> + <translation>/cale/către/executabil, .*/bin/executabil[0-9\.]+$, ...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="158"/> + <source>Action</source> + <translation>Acțiune</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="165"/> + <source>To this port</source> + <translation>Pentru acest port</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="172"/> + <source>To this list of domains</source> + <translation>Pentru această listă de domenii</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="195"/> + <source>You can specify a single IP: +- 192.168.1.1 + +or a regular expression: +- 192\.168\.1\.[0-9]+ + +multiple IPs: +- ^(192\.168\.1\.1|172\.16\.0\.1)$ + +You can also specify a subnet: +- 192.168.1.0/24 + +Note: Commas or spaces are not allowed to separate IPs or networks.</source> + <translation>You can specify a single IP: +- 192.168.1.1 + +or a regular expression: +- 192\.168\.1\.[0-9]+ + +multiple IPs: +- ^(192\.168\.1\.1|172\.16\.0\.1)$ + +You can also specify a subnet: +- 192.168.1.0/24 + +Note: Commas or spaces are not allowed to separate IPs or networks.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="214"/> + <source>LAN</source> + <translation>Rețea locală (LAN)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="219"/> + <source>127.0.0.0/8</source> + <translation>127.0.0.0/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="224"/> + <source>192.168.0.0/24</source> + <translation>192.168.0.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="229"/> + <source>192.168.1.0/24</source> + <translation>192.168.1.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="234"/> + <source>192.168.2.0/24</source> + <translation>192.168.2.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="239"/> + <source>192.168.0.0/16</source> + <translation>192.168.0.0/16</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="244"/> + <source>169.254.0.0/16</source> + <translation>169.254.0.0/16</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="249"/> + <source>172.16.0.0/12</source> + <translation>172.16.0.0/12</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="254"/> + <source>10.0.0.0/8</source> + <translation>10.0.0.0/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="259"/> + <source>::1/128</source> + <translation>::1/128</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="264"/> + <source>fc00::/7</source> + <translation>fc00::/7</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="269"/> + <source>ff00::/8</source> + <translation>ff00::/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="274"/> + <source>fe80::/10</source> + <translation>fe80::/10</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="279"/> + <source>fd00::/8</source> + <translation>fd00::/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="310"/> + <source><html><head/><body><p>You can specify multiple ports using regular expressions:</p><p><br/></p><p>- 53, 80 or 443:</p><p>^(53|80|443)$</p><p><br/></p><p>- 53, 443 or 5551, 5552, 5553, etc:</p><p>^(53|443|555[0-9])$</p></body></html></source> + <translation><html><head/><body><p>You can specify multiple ports using regular expressions:</p><p><br/></p><p>- 53, 80 or 443:</p><p>^(53|80|443)$</p><p><br/></p><p>- 53, 443 or 5551, 5552, 5553, etc:</p><p>^(53|443|555[0-9])$</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="318"/> + <source>once</source> + <translation>o dată</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="323"/> + <source>30s</source> + <translation>30s</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="328"/> + <source>5m</source> + <translation>5m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="333"/> + <source>15m</source> + <translation>15m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="338"/> + <source>30m</source> + <translation>30m</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="343"/> + <source>1h</source> + <translation>1o</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="348"/> + <source>until reboot</source> + <translation>până la repornire</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="353"/> + <source>always</source> + <translation>întotdeauna</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="364"/> + <source>Commas or spaces are not allowed to specify multiple domains. + +Use regular expressions instead: +.*(opensnitch|duckduckgo).com +.*\.google.com + +or a single domain: +www.gnu.org - it'll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ... +gnu.org - it'll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source> + <translation>Commas or spaces are not allowed to specify multiple domains. + +Use regular expressions instead: +.*(opensnitch|duckduckgo).com +.*\.google.com + +or a single domain: +www.gnu.org - it'll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ... +gnu.org - it'll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="375"/> + <source>www.domain.org, .*\.domain.org</source> + <translation>www.domeniu.org, .*\.domeniu.org</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="382"/> + <source>To this host</source> + <translation>Pentru această gazdă</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="396"/> + <source>Duration</source> + <translation>Durată</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="406"/> + <source><html><head/><body><p>Only TCP, UDP or UDPLITE are allowed</p><p>You can use regexp, i.e.: ^(TCP|UDP)$</p></body></html></source> + <translation><html><head/><body><p>Only TCP, UDP or UDPLITE are allowed</p><p>You can use regexp, i.e.: ^(TCP|UDP)$</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="416"/> + <source>TCP</source> + <translation>TCP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="421"/> + <source>UDP</source> + <translation>UDP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="426"/> + <source>UDPLITE</source> + <translation>UDPLITE</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="431"/> + <source>TCP6</source> + <translation>TCP6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="436"/> + <source>UDP6</source> + <translation>UDP6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="441"/> + <source>UDPLITE6</source> + <translation>UDPLITE6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="449"/> + <source>Protocol</source> + <translation>Protocol</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="456"/> + <source>From this executable</source> + <translation>De la acest executabil</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="471"/> + <source>Deny</source> + <translation>Refuză</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="494"/> + <source>Allow</source> + <translation>Permite</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="507"/> + <source>From this command line</source> + <translation>De la această linie de comandă</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="514"/> + <source>From this user ID</source> + <translation>De la acest ID utilizator</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="539"/> + <source><html><head/><body><p>Select a directory with lists of domains to block or allow.</p><p>Put inside that directory files with any extension containing lists of domains.</p><p><br/>The format of each entry of a list is as follow (hosts format):</p><p>127.0.0.1 www.domain.com</p><p>or </p><p>0.0.0.0 www.domain.com</p></body></html></source> + <translation><html><head/><body><p>Select a directory with lists of domains to block or allow.</p><p>Put inside that directory files with any extension containing lists of domains.</p><p><br/>The format of each entry of a list is as follow (hosts format):</p><p>127.0.0.1 www.domain.com</p><p>or </p><p>0.0.0.0 www.domain.com</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="557"/> + <source>Name</source> + <translation>Nume</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="566"/> + <source>Enable</source> + <translation>Activează</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="604"/> + <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them. + +000-allow-localhost +001-deny-broadcast +...</source> + <translation>Regulile sunt verificate în ordine alfabetică, așa că puteți să le numiți ca atare pentru a le prioritiza. + +000-permite-localhost +001-respinge-broadcast +...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="611"/> + <source>leave blank to autocreate</source> + <translation>lăsați gol pentru creare automată</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="620"/> + <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one. + +You must name the rule in such manner that it'll be checked first, because they're checked in alphabetical order. For example: + +[x] Priority - 000-priority-rule +[ ] Priority - 001-less-priority-rule</source> + <translation>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one. + +You must name the rule in such manner that it'll be checked first, because they're checked in alphabetical order. For example: + +[x] Priority - 000-priority-rule +[ ] Priority - 001-less-priority-rule</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="628"/> + <source>Priority rule</source> + <translation>Regulă prioritate</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="648"/> + <source><html><head/><body><p>By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.<br/></p><p>If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.</p></body></html></source> + <translation><html><head/><body><p>By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.<br/></p><p>If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="651"/> + <source>Case-sensitive</source> + <translation>Sensibil la majuscule</translation> + </message> +</context> +<context> + <name>StatsDialog</name> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="34"/> + <source>OpenSnitch Network Statistics</source> + <translation>Statistici de rețea OpenSnitch</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="105"/> + <source>Save to CSV.</source> + <translation>Salvează într-un fișier CSV.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="115"/> + <source>Ctrl+S</source> + <translation>Ctrl+S</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="166"/> + <source>Create a new rule</source> + <translation>Creare regulă nouă</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="196"/> + <source><html><head/><body><p><span style=" font-size:11pt; font-weight:600;">hostname - 192.168.1.1</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-size:11pt; font-weight:600;">hostname - 192.168.1.1</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="235"/> + <source>Status</source> + <translation>Stare</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1697"/> + <source>-</source> + <translation>-</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="279"/> + <source>Start or Stop interception</source> + <translation>Porniți sau opriți interceptarea</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="324"/> + <source>Events</source> + <translation>Evenimente</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="344"/> + <source>Filter</source> + <translation>Filtru</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="357"/> + <source>Allow</source> + <translation>Permite</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="366"/> + <source>Deny</source> + <translation>Refuză</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="384"/> + <source>Ex.: firefox</source> + <translation>De exemplu: firefox</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="411"/> + <source>50</source> + <translation>50</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="416"/> + <source>100</source> + <translation>100</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="421"/> + <source>200</source> + <translation>200</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="426"/> + <source>300</source> + <translation>300</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="439"/> + <source>Delete all intercepted events</source> + <translation>Șterge toate evenimentele de interceptare</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="825"/> + <source>Nodes</source> + <translation>Noduri</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="554"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on the Addr column to view details of a node)</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-size:7pt;">(double click on the Addr column to view details of a node)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1601"/> + <source>Rules</source> + <translation>Reguli</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="610"/> + <source>enable</source> + <translation>Activează</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="617"/> + <source>Edit rule</source> + <translation>Editare regulă</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="631"/> + <source>Delete rule</source> + <translation>Șterge regula</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="699"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on a row to view details of a rule)</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-size:7pt;">(faceți clic dublu pe un rând pentru a vizualiza detaliile regulii)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="692"/> + <source>search rule name</source> + <translation>Căutare nume regulă</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="781"/> + <source>Application rules</source> + <translation>Reguli aplicație</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="796"/> + <source>Permanent</source> + <translation type="unfinished">Permanent</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="810"/> + <source>Temporary</source> + <translation>Temporar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="876"/> + <source>Hosts</source> + <translation>Gazde</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1364"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click to view details of an item)</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-size:7pt;">(faceți clic dublu pentru a vizualiza detaliile unui element)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="984"/> + <source>Applications</source> + <translation>Aplicații</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1051"/> + <source>Delete all intercepted applications</source> + <translation>Șterge toate aplicațiile interceptate</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1109"/> + <source>Addresses</source> + <translation>Adrese</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1211"/> + <source>Ports</source> + <translation>Porturi</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1313"/> + <source>Users</source> + <translation>Utilizatori</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1436"/> + <source>Connections</source> + <translation>Conexiuni</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1491"/> + <source>Dropped</source> + <translation>Aruncate</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1546"/> + <source>Uptime</source> + <translation>Durată activitate</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1671"/> + <source>Version</source> + <translation>Versiune</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="665"/> + <source>Delete connections that matched this rule</source> + <translation>Șterge toate conexiunile care se potrivesc cu această regulă</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="712"/> + <source>All applications</source> + <translation>Toate aplicațiile</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="926"/> + <source>Delete all intercepted hosts</source> + <translation>Șterge toate gazdele interceptate</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1159"/> + <source>Delete all intercepted addresses</source> + <translation>Șterge toate adresele interceptate</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1261"/> + <source>Delete all intercepted ports</source> + <translation>Șterge toate porturile interceptate</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1371"/> + <source>Delete all intercepted users</source> + <translation>Șterge toți utilizatorii interceptați</translation> + </message> +</context> +<context> + <name>contextual_menu</name> + <message> + <location filename="../../../opensnitch/service.py" line="39"/> + <source>Statistics</source> + <translation>Statistici</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="40"/> + <source>Enable</source> + <translation>Activează</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="41"/> + <source>Disable</source> + <translation>Dezactivează</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="42"/> + <source>Help</source> + <translation>Ajutor</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="43"/> + <source>Close</source> + <translation>Închide</translation> + </message> +</context> +<context> + <name>popups</name> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="51"/> + <source>until reboot</source> + <translation>până la repornire</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="53"/> + <source>forever</source> + <translation>mereu</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="89"/> + <source>Allow</source> + <translation>Permite</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="90"/> + <source>Deny</source> + <translation>Refuză</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="265"/> + <source>Outgoing connection</source> + <translation>Conexiune de ieșire</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="270"/> + <source>Process launched from:</source> + <translation>Procesul a fost lansat din:</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="299"/> + <source>from this executable</source> + <translation>de la acest executabil</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="301"/> + <source>from this command line</source> + <translation>de la această linie de comandă</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="305"/> + <source>to port {0}</source> + <translation>către portul {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="364"/> + <source>to {0}</source> + <translation>către {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="308"/> + <source>from user {0}</source> + <translation>de la utilizatorul {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="321"/> + <source>to {0}.*</source> + <translation>către {0}.*</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="374"/> + <source>to *.{0}</source> + <translation>către *.{0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="378"/> + <source>to *{0}</source> + <translation>către *{0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="411"/> + <source><b>Remote</b> process %s running on <b>%s</b></source> + <translation>Procesul %s<b>telecomandat</b> rulează pe <b>%s</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="415"/> + <source>is connecting to <b>%s</b> on %s port %d</source> + <translation>se conectează la <b>%s</b> pe %s portul %d</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="421"/> + <source>is attempting to resolve <b>%s</b> via %s, %s port %d</source> + <translation>încearcă să rezolve <b>%s</b> prin %s, %s portul %d</translation> + </message> +</context> +<context> + <name>preferences</name> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="260"/> + <source>Exception saving config: {0}</source> + <translation>Exception saving config: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="279"/> + <source>Warning</source> + <translation>Avertisment</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="279"/> + <source>You must select a file for the database<br>or choose "In memory" type.</source> + <translation>You must select a file for the database<br>or choose "In memory" type.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="285"/> + <source>DB type changed</source> + <translation>Tipul bazei de date a fost schimbat</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="285"/> + <source>Restart the GUI in order effects to take effect</source> + <translation>Reporniți interfața grafică cu utilizatorul pentru ca modificările să aibă efect</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="340"/> + <source>Applying configuration on {0} ...</source> + <translation>Se aplică configurația pe {0} ...</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="169"/> + <source>Server address can not be empty</source> + <translation>Adresa servitorului nu poate fi goală</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="199"/> + <source>Error loading {0} configuration</source> + <translation>Error loading {0} configuration</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="385"/> + <source>Configuration applied.</source> + <translation>Configurația a fost aplicată.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="387"/> + <source>Error applying configuration: {0}</source> + <translation>Eroare la aplicarea configurației: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="416"/> + <source>Hover the mouse over the texts to display the help<br><br>Don't forget to visit the wiki: <a href="{0}">{0}</a></source> + <translation>Hover the mouse over the texts to display the help<br><br>Don't forget to visit the wiki: <a href="{0}">{0}</a></translation> + </message> +</context> +<context> + <name>proc_details</name> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="96"/> + <source><b>Error loading process information:</b> <br><br> + +</source> + <translation><b>Eroare la încărcarea informațiilor procesului:</b> <br><br> + +</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="115"/> + <source><b>Error stopping monitoring process:</b><br><br></source> + <translation><b>Eroare la oprirea monitorizării procesului:</b><br><br></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="155"/> + <source>loading...</source> + <translation>Se încarcă...</translation> + </message> +</context> +<context> + <name>rules</name> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="124"/> + <source>There're no nodes connected.</source> + <translation>Nu există niciun nod conectat.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="135"/> + <source>Rule applied.</source> + <translation>Regulă aplicată.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="137"/> + <source>Error applying rule: {0}</source> + <translation>Eroare la aplicarea regulii: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="290"/> + <source><b>Error loading rule</b></source> + <translation><b>Eroare la încărcarea regulii</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="395"/> + <source>protocol can not be empty, or uncheck it</source> + <translation>Protocolul nu poate fi gol, sau debifați-l</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="409"/> + <source>Protocol regexp error</source> + <translation>Protocol regexp error</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="413"/> + <source>process path can not be empty</source> + <translation>Calea procesului nu poate fi goală</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="427"/> + <source>Process path regexp error</source> + <translation>Process path regexp error</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="431"/> + <source>command line can not be empty</source> + <translation>Linia de comandă nu poate fi goală</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="445"/> + <source>Command line regexp error</source> + <translation>Command line regexp error</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="449"/> + <source>Dest port can not be empty</source> + <translation>Dest port can not be empty</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="463"/> + <source>Dst port regexp error</source> + <translation>Dst port regexp error</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="467"/> + <source>Dest host can not be empty</source> + <translation>Dest host can not be empty</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="481"/> + <source>Dst host regexp error</source> + <translation>Dst host regexp error</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="485"/> + <source>Dest IP/Network can not be empty</source> + <translation>Dest IP/Network can not be empty</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="507"/> + <source>Dst IP regexp error</source> + <translation>Dst IP regexp error</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="519"/> + <source>User ID can not be empty</source> + <translation>User ID can not be empty</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="533"/> + <source>User ID regexp error</source> + <translation>User ID regexp error</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="537"/> + <source>Lists field cannot be empty</source> + <translation>Lists field cannot be empty</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="539"/> + <source>Lists field must be a directory</source> + <translation>Lists field must be a directory</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="573"/> + <source><b>Rule not supported</b></source> + <translation><b>Regula nu este sprijinită</b></translation> + </message> +</context> +<context> + <name>stats</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="284"/> + <source>Not running</source> + <translation>Nu rulează</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="285"/> + <source>Disabled</source> + <translation>Dezactivată</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="286"/> + <source>Running</source> + <translation>Rulează</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="475"/> + <source>OpenSnitch Network Statistics {0}</source> + <translation>OpenSnitch Network Statistics {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="477"/> + <source>OpenSnitch Network Statistics for {0}</source> + <translation>OpenSnitch Network Statistics for {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="591"/> + <source><b>Error:</b><br><br></source> + <comment>{0}</comment> + <translation><b>Eroare:</b><br><br></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="598"/> + <source>Warning:</source> + <translation>Avertisment:</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="640"/> + <source>Allow</source> + <translation>Permite</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="641"/> + <source>Deny</source> + <translation>Refuză</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="644"/> + <source>Always</source> + <translation>Întotdeauna</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="645"/> + <source>Until reboot</source> + <translation>Până la repornire</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="653"/> + <source>Disable</source> + <translation>Dezactivează</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="655"/> + <source>Enable</source> + <translation>Activează</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="658"/> + <source>Duplicate</source> + <translation>Duplică</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="659"/> + <source>Edit</source> + <translation>Editare</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="660"/> + <source>Delete</source> + <translation>Șterge</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="668"/> + <source> Your are about to delete this rule. </source> + <translation> Sunteți pe cale să ștergeți aceasă regulă. </translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1003"/> + <source> Are you sure?</source> + <translation> Sigur doriți acest lucru?</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="786"/> + <source>Rule not found by that name and node</source> + <translation>Regula nu a putut fi găsită după acel nume și nod</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1003"/> + <source> You are about to delete this rule. </source> + <translation> Sunteți pe cale să ștergeți această regulă. </translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1490"/> + <source>Save as CSV</source> + <translation>Save as CSV</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="261"/> + <source>Name</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Nume</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="262"/> + <source>Address</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Adresă</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="263"/> + <source>Status</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Stare</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="264"/> + <source>Hostname</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Nume gazdă</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="265"/> + <source>Version</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Versiune</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="266"/> + <source>Rules</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Reguli</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="267"/> + <source>Time</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Timp</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="268"/> + <source>Action</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Acțiune</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="269"/> + <source>Duration</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Durată</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="270"/> + <source>Node</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Nod</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="271"/> + <source>Enabled</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Activată</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="272"/> + <source>Hits</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Atingeri</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="273"/> + <source>Protocol</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Protocol</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="274"/> + <source>Process</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Proces</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="276"/> + <source>Destination</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Destinație</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="280"/> + <source>Rule</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Regulă</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="281"/> + <source>UserID</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>IdentificatorUtilizator</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="282"/> + <source>LastConnection</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>UltimaConexiune</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="275"/> + <source>Args</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Argumente</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="277"/> + <source>DstIP</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>DstIP</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="278"/> + <source>DstHost</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>DstHost</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="279"/> + <source>DstPort</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>DstPort</translation> + </message> +</context> +</TS> diff --git a/ui/i18n/locales/ru_RU/opensnitch-ru_RU.ts b/ui/i18n/locales/ru_RU/opensnitch-ru_RU.ts new file mode 100644 index 0000000..e62febc --- /dev/null +++ b/ui/i18n/locales/ru_RU/opensnitch-ru_RU.ts @@ -0,0 +1,2458 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="ru_RU"> +<context> + <name>Dialog</name> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="34"/> + <source>opensnitch-qt</source> + <translation>opensnitch-qt</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="299"/> + <source>User ID</source> + <translation>ID пользователя</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="333"/> + <source><html><head/><body><p><span style=" font-weight:600;">Executed from</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-weight:600;">Выполнено из</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="630"/> + <source>TextLabel</source> + <translation>Заметка</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="426"/> + <source>Source IP</source> + <translation>Исходный IP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="449"/> + <source>Process ID</source> + <translation>ID процесса</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="582"/> + <source>Destination IP</source> + <translation>IP назначения</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="605"/> + <source>Dst Port</source> + <translation>Порт назначения</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="679"/> + <source>from this executable</source> + <translation>из этого исполняемого файла</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="684"/> + <source>from this command line</source> + <translation>из этой командной строки</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="689"/> + <source>this destination port</source> + <translation>этот порт назначения</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="694"/> + <source>this user</source> + <translation>этот пользователь</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="699"/> + <source>this destination ip</source> + <translation>этот ip-адрес назначения</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="728"/> + <source>once</source> + <translation>один раз</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="733"/> + <source>30s</source> + <translation>30 секунд</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="738"/> + <source>5m</source> + <translation>5 минут</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="743"/> + <source>15m</source> + <translation>15 минут</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="748"/> + <source>30m</source> + <translation>30 минут</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="753"/> + <source>1h</source> + <translation>1 час</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="706"/> + <source>for this session</source> + <translation type="obsolete">durante esta sesión</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="763"/> + <source>forever</source> + <translation>постоянно</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="789"/> + <source>Deny</source> + <translation>Запретить</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="818"/> + <source>Allow</source> + <translation>Разрешить</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="847"/> + <source>+</source> + <translation>+</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="758"/> + <source>until reboot</source> + <translation>до перезагрузки</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="704"/> + <source>from this PID</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>PreferencesDialog</name> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="14"/> + <source>Preferences</source> + <translation>Настройки</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="472"/> + <source>UI</source> + <translation>Пользовательский интерфейс</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="54"/> + <source><html><head/><body><p>This timeout is the countdown you see when a pop-up dialog is shown.</p></body></html></source> + <translation type="obsolete">Este timeout es la cuenta atrás que aparece cuando se muestra una ventana emergente</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="454"/> + <source>Default timeout</source> + <translation>Тайм-аут по умолчанию</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="331"/> + <source>Pop-up default duration</source> + <translation>Продолжительность всплывающего окна по умолчанию</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="777"/> + <source>Default duration</source> + <translation>Продолжительность по умолчанию</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="162"/> + <source>Pop-up default action</source> + <translation type="obsolete">Acción por defecto de la ventana emergente</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="483"/> + <source>Default action</source> + <translation type="obsolete">Acción por defecto</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="314"/> + <source>Default target</source> + <translation>Цель по умолчанию</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="170"/> + <source>center</source> + <translation>в центре</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="175"/> + <source>top right</source> + <translation>в правом верхнем углу</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="180"/> + <source>bottom right</source> + <translation>в нижнем правом углу</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="185"/> + <source>top left</source> + <translation>в левом верхнем углу</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="190"/> + <source>bottom left</source> + <translation>в нижнем левом углу</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="167"/> + <source>Prompt dialog default position on screen</source> + <translation type="obsolete">Posición por defecto</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="274"/> + <source>by executable</source> + <translation>исполняемым файлом</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="279"/> + <source>by command line</source> + <translation>по командной строке</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="284"/> + <source>by destination port</source> + <translation>по порту назначения</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="289"/> + <source>by destination ip</source> + <translation>по IP-адресу назначения</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="294"/> + <source>by user id</source> + <translation>по ID пользователя</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="881"/> + <source>once</source> + <translation>один раз</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="210"/> + <source>30s</source> + <translation>30 секунд</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="215"/> + <source>5m</source> + <translation>5 минут</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="220"/> + <source>15m</source> + <translation>15 минут</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="225"/> + <source>30m</source> + <translation>30 минут</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="230"/> + <source>1h</source> + <translation>1 час</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="240"/> + <source>for this session</source> + <translation type="obsolete">durante esta sesión</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="240"/> + <source>forever</source> + <translation>постоянно</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="923"/> + <source>deny</source> + <translation>запрещено</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="932"/> + <source>allow</source> + <translation>разрешено</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="406"/> + <source>Disable pop-ups, only display an alert</source> + <translation type="obsolete">Отключить всплывающие окна, отображать только предупреждение</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="734"/> + <source>Nodes</source> + <translation>Узлы</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="740"/> + <source>Process monitor method</source> + <translation>Метод мониторинга процесса</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="774"/> + <source><html><head/><body><p>The default duration will take place when there's no UI connected.</p></body></html></source> + <translation><html><head/><body><p>Продолжительность по умолчанию будет иметь место, когда пользовательский интерфейс не подключен.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="899"/> + <source><html><head/><body><p>Address of the node.</p><p>Default: unix:///tmp/osui.sock (unix:// is mandatory if it's a Unix socket)</p><p>It can also be an IP address with the port: 127.0.0.1:50051</p></body></html></source> + <translation><html><head/><body><p>Адрес узла.</p><p>По умолчанию: unix:///tmp/osui.sock (unix:// является обязательным, если это Unix сокет) </p><p>Это также может быть IP-адрес с портом: 127.0.0.1:50051</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="902"/> + <source>Address</source> + <translation>Адрес</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1042"/> + <source>Default log level</source> + <translation>Уровень логирования по умолчанию</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="950"/> + <source>Version</source> + <translation>Версия</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="813"/> + <source><html><head/><body><p>The default action will take place when there's no UI connected.</p></body></html></source> + <translation><html><head/><body><p>Действие по умолчанию выполняется при отсутствии подключенного пользовательского интерфейса.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="757"/> + <source><html><head/><body><p>Log file to write logs.<br/></p><p>/dev/stdout will print logs to the standard output.</p></body></html></source> + <translation><html><head/><body><p>Лог файл для логирования.<br/></p><p>/dev/stdout выводит логи на стандартный вывод.</p></body> </html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="760"/> + <source>Log file</source> + <translation>Лог файл</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="578"/> + <source><html><head/><body><p>If checked, opensnitch will prompt you to allow or deny connections that don't have an asocciated PID, due to several reasons.</p><p>The pop-up dialog will only contain information about the network connection.</p></body></html></source> + <translation type="obsolete">Si marcas esta opción, OpenSnitch te preguntará para Aceptar o Denegar conexiones que no tengan un PID asociado por diferentes razones. + +La ventana emergente sólo contendrá información relativa a la conexión. + +Nota: Estas conexiones no tienen por qué indicar que algo sospechoso está sucediendo. Simplemente +es que no hemos descubierto el PID (por ejemplo conexiones que no se originan en la máquina, o paquetes en mal estado).</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="581"/> + <source>Intercept Unknown Connections</source> + <translation type="obsolete">Interceptar conexiones desconocidas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="832"/> + <source>HostName</source> + <translation>Имя хоста</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1001"/> + <source>unix:///tmp/osui.sock</source> + <translation>unix:///tmp/osui.sock</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="886"/> + <source>until restart</source> + <translation>до перезапуска</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="891"/> + <source>always</source> + <translation>всегда</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1013"/> + <source>/var/log/opensnitchd.log</source> + <translation>/var/log/opensnitchd.log</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1018"/> + <source>/dev/stdout</source> + <translation>/dev/stdout</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="790"/> + <source>Apply configuration to all nodes</source> + <translation>Применить конфигурацию ко всем узлам</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1057"/> + <source>Database</source> + <translation>База данных</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1092"/> + <source>In memory</source> + <translation>В памяти</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1097"/> + <source>File</source> + <translation>Файл</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1363"/> + <source>Close</source> + <translation>Закрыть</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1374"/> + <source>Apply</source> + <translation>Применить</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1385"/> + <source>Save</source> + <translation>Сохранить</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="235"/> + <source>until reboot</source> + <translation>до перезагрузки</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1111"/> + <source>Database type</source> + <translation>Тип базы данных</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1118"/> + <source>Select</source> + <translation>Выбрать</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="83"/> + <source>Pop-ups default options</source> + <translation type="obsolete">Opciones por defecto de las ventanas emergentes</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="367"/> + <source>Pop-ups default position on screen</source> + <translation type="obsolete">Posición en pantalla</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="102"/> + <source><html><head/><body><p>The advanced view allows you to apply more filters on a connection</p><p>when a pop-up appears.</p></body></html></source> + <translation type="obsolete">La vista avanzada permite filtrar conexiones por más parámetros</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="350"/> + <source>Show advanced view by default</source> + <translation>Показывать расширенный вид по умолчанию</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="660"/> + <source>Action</source> + <translation>Действие</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="366"/> + <source><html><head/><body><p>If checked, the pop-ups will be displayed with the advanced view active.</p></body></html></source> + <translation><html><head/><body><p>Если этот флажок установлен, всплывающие окна будут отображаться с активным расширенным видом.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="334"/> + <source>Duration</source> + <translation>Длительность</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="254"/> + <source><html><head/><body><p>By default when a new pop-up appears, in its simplest form, you'll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).</p><p>With these options, you can choose multiple fields to filter connections for.</p></body></html></source> + <translation><html><head/><body><p>По умолчанию, когда появляется новое всплывающее окно, в его простейшей форме вы сможете фильтровать соединения или приложения по одному свойству соединения (исполняемый файл, порт, IP-адрес и т. д.).</p><p>С помощью этих вариантов, вы можете выбрать несколько полей для фильтрации подключений.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="257"/> + <source>Filter connections also by:</source> + <translation>Также фильтровать соединения по:</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="362"/> + <source>If checked, this field will be checked when a pop-up is displayed</source> + <translation type="obsolete">Si lo seleccionas, este campo se usará para filtrar las conexiones</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="81"/> + <source>User ID</source> + <translation>ID пользователя</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="97"/> + <source>Destination port</source> + <translation>Порт назначения</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="113"/> + <source>Destination IP</source> + <translation>IP назначения</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="451"/> + <source><html><head/><body><p>This timeout is the countdown you see when a pop-up dialog is shown.</p><p>If the pop-up is not answered, the default options will be applied.</p></body></html></source> + <translation><html><head/><body><p>Этот тайм-аут представляет собой обратный отсчет, который вы видите, когда отображается всплывающее диалоговое окно.</p><p>Если всплывающее окно не отвечает, будут применены параметры по умолчанию.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="347"/> + <source>The advanced view allows you to easily select multiple fields to filter connections</source> + <translation>Расширенный вид позволяет легко выбирать несколько полей для фильтрации подключений.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="110"/> + <source>If checked, this field will be selected when a pop-up is displayed</source> + <translation>Если флажок установлен, это поле будет выбрано при отображении всплывающего окна.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="150"/> + <source><html><head/><body><p>Pop-up default action.</p><p>When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.</p><p><br/></p><p>While a pop-up is asking the user to allow or deny a connection:</p><p>1. new outgoing connections are denied.</p><p>2. known connections are allowed or denied based on the rules defined by the user.</p></body></html></source> + <translation><html><head/><body><p>Действие всплывающего окна по умолчанию.</p><p>Когда будет установлено новое исходящее соединение, это действие будет выбрано по умолчанию, поэтому, если тайм-аут срабатывает , будет применен этот параметр.</p><p><br/></p><p>Когда всплывающее окно просит пользователя разрешить или запретить соединение:</p><p >1. новые исходящие соединения запрещены.</p><p>2. известные соединения разрешаются или запрещаются на основе правил, определенных пользователем.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="816"/> + <source>Default action when the GUI is disconnected</source> + <translation>Обычное действие, когда интерфейс отключен</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="912"/> + <source>Debug invalid connections</source> + <translation>Отладка недействительных соединений</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="39"/> + <source>Pop-ups</source> + <translation>Всплывающие окна</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="64"/> + <source>Default options</source> + <translation>Параметры по умолчанию</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="321"/> + <source>Default position on screen</source> + <translation>Положение по умолчанию на экране</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="713"/> + <source>any temporary rules</source> + <translation>любые временные правила</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="478"/> + <source><html><head/><body><p>When this option is selected, the rules of the selected duration won't be added to the list of temporary rules in the GUI.</p><p><br/></p><p>Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</p></body></html></source> + <translation><html><head/><body><p>Если выбран этот параметр, правила выбранной продолжительности не будут добавляться в список временных правил в графическом интерфейсе.</p><p><br/></p><p>Временные правила останутся в силе, и вы сможете использовать их, когда будет предложено разрешить/запретить новое подключение.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="481"/> + <source>Don't save rules of duration</source> + <translation>Не сохранять правила длительности</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="463"/> + <source>Show events columns</source> + <translation type="obsolete">Mostrar columnas de la pestaña Eventos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="596"/> + <source>Time</source> + <translation>Время</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="676"/> + <source>Destination</source> + <translation>Назначение</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="644"/> + <source>Protocol</source> + <translation>Протокол</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="692"/> + <source>Process</source> + <translation>Процесс</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="612"/> + <source>Rule</source> + <translation>Правило</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="628"/> + <source>Node</source> + <translation>Узел</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="723"/> + <source><html><head/><body><p>If checked, opensnitch will prompt you to allow or deny connections that don't have an asocciated PID, due to several reasons, mostly due to bad state connections.</p><p>The pop-up dialog will only contain information about the network connection.</p><p>There're some scenarios where these are valid connections though, like when establishing a VPN using wireguard.</p></body></html></source> + <translation type="obsolete"><html><head/><body><p>Eсли этот флажок установлен, opensnitch предложит вам разрешить или запретить соединения, не имеющие связанного PID, по нескольким причинам, в основном из-за плохого состояния соединений.</p> <p>Всплывающее диалоговое окно будет содержать только информацию о сетевом подключении.</p><p>Хотя в некоторых сценариях это действительные подключения, например, при установке VPN с помощью wireguard.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="584"/> + <source>Events tab columns</source> + <translation>Столбцы вкладки "События"</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="299"/> + <source>by PID</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="461"/> + <source>Disable pop-ups, only display an notification</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="494"/> + <source>Desktop notifications</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="512"/> + <source>Use system notifications</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="528"/> + <source>Use Qt notifications</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="557"/> + <source>Test</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="570"/> + <source>System</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="705"/> + <source>Theme</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="909"/> + <source><html><head/><body><p>If checked, OpenSnitch will prompt you to allow or deny connections that don't have an associated PID, due to several reasons, mostly due to bad state connections.</p><p>The pop-up dialog will only contain information about the network connection.</p><p>There're some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1196"/> + <source>minutes</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1222"/> + <source>Minutes between events purges</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1245"/> + <source>days</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1255"/> + <source>Maximum days of events to keep</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>ProcessDetailsDialog</name> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="14"/> + <source>Process details</source> + <translation>Детали процесса</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="61"/> + <source>loading...</source> + <translation>загрузка...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="81"/> + <source>CWD: loading...</source> + <translation>CWD: загрузка...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="93"/> + <source>mem stats: loading...</source> + <translation>статистика памяти: загружается...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="121"/> + <source>Status</source> + <translation>Статус</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="135"/> + <source>Open files</source> + <translation>Открыть файлы</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="149"/> + <source>I/O Statistics</source> + <translation>I/O Статистика</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="163"/> + <source>Memory mapped files</source> + <translation>Файлы с отображением памяти</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="177"/> + <source>Stack</source> + <translation>Стек</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="191"/> + <source>Environment variables</source> + <translation>Переменные среды</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="210"/> + <source>Application pids</source> + <translation>PIDы приложений</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="240"/> + <source>Start or stop monitoring this process</source> + <translation>Начать или остановить мониторинг этого процесса</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="256"/> + <source>Close</source> + <translation>Закрыть</translation> + </message> +</context> +<context> + <name>RulesDialog</name> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="20"/> + <source>Rule</source> + <translation>Правило</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="852"/> + <source>Node</source> + <translation>Узел</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="875"/> + <source>Apply rule to all nodes</source> + <translation>Применить правило ко всем узлам</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="254"/> + <source>From this command line</source> + <translation>Из этой командной строки</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="341"/> + <source>From this executable</source> + <translation>Из этого исполняемого файла</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="56"/> + <source>Action</source> + <translation>Действие</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="138"/> + <source>/path/to/executable, .*/bin/executable[0-9\.]+$, ...</source> + <translation type="obsolete">/путь/к/исполняемому/файлу, .*/bin/executable[0-9\.]+$, ...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="456"/> + <source>To this IP / Network</source> + <translation>К этому IP / Сети</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="97"/> + <source>once</source> + <translation>один раз</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="102"/> + <source>30s</source> + <translation>30 секунд</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="107"/> + <source>5m</source> + <translation>5 минут</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="112"/> + <source>15m</source> + <translation>15 минут</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="117"/> + <source>30m</source> + <translation>30 минут</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="122"/> + <source>1h</source> + <translation>1 час</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="230"/> + <source>until restart</source> + <translation type="obsolete">hasta reiniciar (el servicio)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="132"/> + <source>always</source> + <translation>всегда</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="366"/> + <source>To this port</source> + <translation>К этому порту</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="247"/> + <source>From this user ID</source> + <translation>От этого ID пользователя</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="492"/> + <source>Commas or spaces are not allowed to specify multiple domains. + +Use regular expressions instead: +.*(opensnitch|duckduckgo).com +.*\.google.com + +or a single domain: +www.gnu.org - it'll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ... +gnu.org - it'll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source> + <translation>Запятые или пробелы не могут указывать несколько доменов. + +Вместо этого используйте регулярные выражения: +.*(opensnitch|duckduckgo).com +.*\.google.com + +или один домен: +www.gnu.org - это будет соответствовать только www.gnu.org, но не ftp.gnu.org, и не www2.gnu.org,... +gnu.org - это будет соответствовать только gnu.org, но не www.gnu.org, и не ftp.gnu.org, ...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="503"/> + <source>www.domain.org, .*\.domain.org</source> + <translation>www.domain.org, .*\.domain.org</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="396"/> + <source><html><head/><body><p>Only TCP, UDP or UDPLITE are allowed</p><p>You can use regexp, i.e.: ^(TCP|UDP)$</p></body></html></source> + <translation><html><head/><body><p>Только TCP, UDP или UDPLITE разрешены</p><p>Вы можете использовать regexp: ^(TCP|UDP)$</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="406"/> + <source>TCP</source> + <translation>TCP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="411"/> + <source>UDP</source> + <translation>UDP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="416"/> + <source>UDPLITE</source> + <translation>UDPLITE</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="421"/> + <source>TCP6</source> + <translation>TCP6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="426"/> + <source>UDP6</source> + <translation>UDP6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="431"/> + <source>UDPLITE6</source> + <translation>UDPLITE6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="513"/> + <source>You can specify a single IP: +- 192.168.1.1 + +or a regular expression: +- 192\.168\.1\.[0-9]+ + +multiple IPs: +- ^(192\.168\.1\.1|172\.16\.0\.1)$ + +You can also specify a subnet: +- 192.168.1.0/24 + +Note: Commas or spaces are not allowed to separate IPs or networks.</source> + <translation>Вы можете указать один IP: +- 192.168.1.1 + +или регулярное выражение: +- 192\.168\.1\.[0-9]+ + +несколько IP-адресов: +- ^(192\.168\.1\.1|172\.16\.0\.1)$ + +Вы также можете указать подсеть: +- 192.168.1.0/24 + +Примечание. Запятые или пробелы не могут использоваться для разделения IP-адресов или сетей.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="532"/> + <source>LAN</source> + <translation>LAN</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="537"/> + <source>127.0.0.0/8</source> + <translation>127.0.0.0/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="542"/> + <source>192.168.0.0/24</source> + <translation>192.168.0.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="547"/> + <source>192.168.1.0/24</source> + <translation>192.168.1.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="552"/> + <source>192.168.2.0/24</source> + <translation>192.168.2.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="557"/> + <source>192.168.0.0/16</source> + <translation>192.168.0.0/16</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="562"/> + <source>169.254.0.0/16</source> + <translation>169.254.0.0/16</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="567"/> + <source>172.16.0.0/12</source> + <translation>172.16.0.0/12</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="572"/> + <source>10.0.0.0/8</source> + <translation>10.0.0.0/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="577"/> + <source>::1/128</source> + <translation>::1/128</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="582"/> + <source>fc00::/7</source> + <translation>fc00::/7</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="587"/> + <source>ff00::/8</source> + <translation>ff00::/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="592"/> + <source>fe80::/10</source> + <translation>fe80::/10</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="597"/> + <source>fd00::/8</source> + <translation>fd00::/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="89"/> + <source>Duration</source> + <translation>Длительность</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="449"/> + <source>Protocol</source> + <translation>Протокол</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="373"/> + <source>To this host</source> + <translation>К этому хосту</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="151"/> + <source>Deny</source> + <translation>Запретить</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="191"/> + <source>Allow</source> + <translation>Разрешить</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="912"/> + <source>Name</source> + <translation>Имя</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="884"/> + <source>Enable</source> + <translation>Включить</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="928"/> + <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them. + +000-allow-localhost +001-deny-broadcast +...</source> + <translation>Правила проверяются в алфавитном порядке, поэтому вы можете назвать их соответствующим образом, чтобы расставить приоритеты. + +000-allow-localhost +001-deny-broadcast +...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="935"/> + <source>leave blank to autocreate</source> + <translation>оставьте пустым для автосоздания</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="891"/> + <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one. + +You must name the rule in such manner that it'll be checked first, because they're checked in alphabetical order. For example: + +[x] Priority - 000-priority-rule +[ ] Priority - 001-less-priority-rule</source> + <translation>Если этот флажок установлен, это правило будет иметь приоритет над остальными правилами. Никакие другие правила не будут проверяться после этого. + +Вы должны назвать правило таким образом, чтобы оно проверялось первым, потому что они проверяются в алфавитном порядке. Например: + +[x] Priority - 000-priority-rule +[ ] Priority - 001-less-priority-rule</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="899"/> + <source>Priority rule</source> + <translation>Правило приоритета</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="770"/> + <source><html><head/><body><p>By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.<br/></p><p>If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.</p></body></html></source> + <translation><html><head/><body><p>По умолчанию поле правил не чувствительно к регистру, т. е. если процесс пытается получить доступ к gOOgle.CoM, а у вас есть правило Запретить .*google.com, соединение будет заблокировано.<br/></p><p>Если вы установите этот флажок, вы должны указать точную строку (домен, исполняемый файл, командную строку), которую вы хотите отфильтровать.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="773"/> + <source>Case-sensitive</source> + <translation>Чувствительно к регистру</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="442"/> + <source><html><head/><body><p>You can specify multiple ports using regular expressions:</p><p><br/></p><p>- 53, 80 or 443:</p><p>^(53|80|443)$</p><p><br/></p><p>- 53, 443 or 5551, 5552, 5553, etc:</p><p>^(53|443|555[0-9])$</p></body></html></source> + <translation><html><head/><body><p>Вы можете указать несколько портов, используя регулярные выражения:</p><p><br/></p><p>- 53, 80 или 443:</p><p>^(53|80|443)$</p><p><br/></p><p>- 53, 443 или 5551, 5552, 5553, итд:</p><p>^(53|443|555[0-9])$</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="127"/> + <source>until reboot</source> + <translation>до перезагрузки</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="658"/> + <source>To this list of domains</source> + <translation>К этому списку доменов</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="539"/> + <source><html><head/><body><p>Select a directory with lists of domains to block or allow.</p><p>Put inside that directory files with any extension containing lists of domains.</p><p><br/>The format of each entry of a list is as follow (hosts format):</p><p>127.0.0.1 www.domain.com</p><p>or </p><p>0.0.0.0 www.domain.com</p></body></html></source> + <translation type="obsolete"><html><head/><body><p>Выберите каталог со списками доменов, которые нужно заблокировать или разрешить.</p><p>Поместите в этот каталог файлы с любым расширением, содержащие списки доменов.</p><p><br/>Формат каждой записи списка следующий (формат хостов):</p><p>127.0.0.1 www.domain.com</p><p>или </p><p>0.0.0.0 www.domain.com</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="148"/> + <source>Deny will just discard the connection</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="165"/> + <source>Reject will drop the connection, and kill the socket that initiated it</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="168"/> + <source>Reject</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="185"/> + <source>Allow will allow the connection</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="221"/> + <source>Applications</source> + <translation type="unfinished">Приложения</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="230"/> + <source><html><head/><body><p>The value of this field is always the absolute path to the executable: /path/to/binary<br/></p><p>Examples:</p><p>- Simple: /path/to/binary</p><p>- Multiple paths: ^/usr/lib(64|)/firefox/firefox$</p><p>- Multiple binaries: ^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ </p><p>- Deny/Allow executions from /tmp:</p><p>^/(var/|)tmp/.*$<br/></p><p>For more examples visit the <a href="https://github.com/evilsocket/opensnitch/wiki/Rules-examples">wiki page</a> or ask on the <a href="https://github.com/evilsocket/opensnitch/discussions">Discussion forums</a>.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="240"/> + <source>Is regular expression</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="264"/> + <source><html><head/><body><p>This field will contain and match the command line that was executed by the user.<br/></p><p>If the user typed the command, only the command will appear:</p><p>telnet 1.2.3.4<br/></p><p>If the user typed the absolute or relative path to the command, that is what will appear:</p><p>/usr/bin/telnet 1.2.3.4</p><p>../../../usr/bin/telnet 1.2.3.4</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="274"/> + <source>From this PID</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="348"/> + <source>is regular expression</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="360"/> + <source>Network</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="610"/> + <source>List of domains/IPs</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="616"/> + <source>To this list of network ranges</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="623"/> + <source>To this list of IPs</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="649"/> + <source><html><head/><body><p>Select a directory with files containing list of IPs to block or allow:</p><p>1.2.3.4.5</p><p>1.2.3.4.6</p><p>.</p><p>etc.</p><p>One IP per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="684"/> + <source><html><head/><body><p>Select a directory with files containing list of network ranges to block or allow:</p><p>1.2.3.0/24</p><p>80.34.56.0/20</p><p>.</p><p>etc.<br/></p><p>One Network Range per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="712"/> + <source><html><head/><body><p>Select a directory with lists of domains to block or allow.</p><p>Put inside that directory files with any extension containing lists of domains.</p><p><br/>The format of each entry of a list is as follow (hosts format):</p><p>127.0.0.1 www.domain.com</p><p>or </p><p>0.0.0.0 www.domain.com</p><p>Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="727"/> + <source>To this list of domains +(regular expressions)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="754"/> + <source><html><head/><body><p>Select a directory with files containing regular expressions of domains to block or allow:</p><p>.*\.example\.com</p><p>You can also use a domain as is: &quot;example.com&quot; , and it'll match whatever.example.com, whatever.example.com.localdomain, etc.</p><p>One domain per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="764"/> + <source>More</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>StatsDialog</name> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="34"/> + <source>OpenSnitch Network Statistics</source> + <translation>Сетевая статистика OpenSnitch</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="284"/> + <source>Save to CSV.</source> + <translation>Сохранить в CSV.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="294"/> + <source>Ctrl+S</source> + <translation>Ctrl+S</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="330"/> + <source>Create a new rule</source> + <translation>Создать новое правило</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="360"/> + <source><html><head/><body><p><span style=" font-size:11pt; font-weight:600;">hostname - 192.168.1.1</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-size:11pt; font-weight:600;">hostname - 192.168.1.1</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="399"/> + <source>Status</source> + <translation>Статус</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1627"/> + <source>-</source> + <translation>-</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="437"/> + <source>Start or Stop interception</source> + <translation>Начать или остановить перехват</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="482"/> + <source>Events</source> + <translation>События</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="94"/> + <source>Filter</source> + <translation>Фильтр</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="107"/> + <source>Allow</source> + <translation>Разрешить</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="116"/> + <source>Deny</source> + <translation>Запретить</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="143"/> + <source>Ex.: firefox</source> + <translation>Ex.: фаерфокс</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="199"/> + <source>50</source> + <translation>50</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="204"/> + <source>100</source> + <translation>100</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="209"/> + <source>200</source> + <translation>200</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="214"/> + <source>300</source> + <translation>300</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="724"/> + <source>Nodes</source> + <translation>Узлы</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="554"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on the Addr column to view details of a node)</span></p></body></html></source> + <translation type="obsolete"><html><head/><body><p><span style=" font-size:7pt;">(дважды щелкните столбец Адреса, чтобы просмотреть сведения об узле)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1531"/> + <source>Rules</source> + <translation>Правила</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="833"/> + <source>enable</source> + <translation>включить</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="684"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on the Name column to view details of a rule)</span></p></body></html></source> + <translation type="obsolete">(doble click en la columna Nombre para ver los detalles)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="692"/> + <source>search rule name</source> + <translation type="obsolete">искать название правила</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="680"/> + <source>Application rules</source> + <translation>Правила приложений</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="782"/> + <source>Permanent</source> + <translation>Постоянно</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="791"/> + <source>Temporary</source> + <translation>Временно</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="895"/> + <source>Hosts</source> + <translation>Хосты</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1364"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click to view details of an item)</span></p></body></html></source> + <translation type="obsolete"><html><head/><body><p><span style=" font-size:7pt;">(дважды щелкните, чтобы просмотреть сведения об элементе)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="982"/> + <source>Applications</source> + <translation>Приложения</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1089"/> + <source>Addresses</source> + <translation>Адреса</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1176"/> + <source>Ports</source> + <translation>Порты</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1260"/> + <source>Users</source> + <translation>Пользователи</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1366"/> + <source>Connections</source> + <translation>Соединения</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1421"/> + <source>Dropped</source> + <translation>Сброшено</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1476"/> + <source>Uptime</source> + <translation>Время работы</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1601"/> + <source>Version</source> + <translation>Версия</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="227"/> + <source>Delete all intercepted events</source> + <translation>Удалить все перехваченные события</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="840"/> + <source>Edit rule</source> + <translation>Редактировать правило</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="854"/> + <source>Delete rule</source> + <translation>Удалить правило</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="926"/> + <source>Delete all intercepted hosts</source> + <translation type="obsolete">Удалить всю информацию о перехваченных хостах</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1051"/> + <source>Delete all intercepted applications</source> + <translation type="obsolete">Удалить всю информацию о перехваченных приложениях</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1159"/> + <source>Delete all intercepted addresses</source> + <translation type="obsolete">Удалить всю информацию о перехваченных адресах</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1261"/> + <source>Delete all intercepted ports</source> + <translation type="obsolete">Удалить всю информацию о перехваченных портах</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1371"/> + <source>Delete all intercepted users</source> + <translation type="obsolete">Удалить всю информацию о перехваченных пользователях</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="699"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on a row to view details of a rule)</span></p></body></html></source> + <translation type="obsolete"><html><head/><body><p><span style=" font-size:7pt;">(дважды щелкните строку, чтобы просмотреть сведения о правиле)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="665"/> + <source>Delete connections that matched this rule</source> + <translation type="obsolete">Удалить подключения, соответствующие этому правилу</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="773"/> + <source>All applications</source> + <translation>Все приложения</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="125"/> + <source>Reject</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="177"/> + <source>0</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>contextual_menu</name> + <message> + <location filename="../../../opensnitch/service.py" line="43"/> + <source>Statistics</source> + <translation>Статистика</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="46"/> + <source>Help</source> + <translation>Помощь</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="47"/> + <source>Close</source> + <translation>Закрыть</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="44"/> + <source>Enable</source> + <translation>Включить</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="45"/> + <source>Disable</source> + <translation>Выключить</translation> + </message> +</context> +<context> + <name>menu_close</name> + <message> + <location filename="../../../opensnitch/service.py" line="131"/> + <source>Close</source> + <translation type="obsolete">Cerrar</translation> + </message> +</context> +<context> + <name>menu_help</name> + <message> + <location filename="../../../opensnitch/service.py" line="126"/> + <source>Help</source> + <translation type="obsolete">Ayuda</translation> + </message> +</context> +<context> + <name>menu_statistics</name> + <message> + <location filename="../../../opensnitch/service.py" line="120"/> + <source>Statistics</source> + <translation type="obsolete">Eventos</translation> + </message> +</context> +<context> + <name>notifications</name> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="547"/> + <source>System notifications are not available, you need to install python3-notify2.</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>popups</name> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="91"/> + <source>Allow</source> + <translation>Разрешить</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="92"/> + <source>Deny</source> + <translation>Запретить</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="54"/> + <source>forever</source> + <translation>навсегда</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="285"/> + <source>Outgoing connection</source> + <translation>Исходящее соединение</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="290"/> + <source>Process launched from:</source> + <translation>Процесс запущен из:</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="325"/> + <source>from this command line</source> + <translation>из этой командной строки</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="321"/> + <source>from this executable</source> + <translation>из этого исполняемого файла</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="208"/> + <source>Unknown process</source> + <translation type="obsolete">Proceso no encontrado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="52"/> + <source>until reboot</source> + <translation>до перезагрузки</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="327"/> + <source>to port {0}</source> + <translation>в порт {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="222"/> + <source><b>%s</b> is connecting to <b>%s</b> on %s port %d</source> + <translation type="obsolete"><b>%s</b> está conectándose a <b>%s</b> en el puerto %s %d</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="228"/> + <source><b>Remote</b> process <b>%s</b> running on <b>%s</b> is connecting to <b>%s</b> on %s port %d</source> + <translation type="obsolete">El proceso <b>remoto %s</b> ejecutándose en <b>%s</b> está conectándose a <b>%s</b> en el puerto %s %d</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="390"/> + <source>to {0}</source> + <translation>в {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="330"/> + <source>from user {0}</source> + <translation>от пользователя {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="347"/> + <source>to {0}.*</source> + <translation>в {0}.*</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="400"/> + <source>to *.{0}</source> + <translation>в *.{0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="378"/> + <source>to *{0}</source> + <translation type="obsolete">в *{0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="434"/> + <source><b>Remote</b> process %s running on <b>%s</b></source> + <translation><b>Удаленный</b> процесс %s запущенный на <b>%s</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="438"/> + <source>is connecting to <b>%s</b> on %s port %d</source> + <translation>подключается к <b>%s</b> через %s порт %d</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="444"/> + <source>is attempting to resolve <b>%s</b> via %s, %s port %d</source> + <translation>пытается разрешить <b>%s</b> через %s, %s порт %d</translation> + </message> + <message> + <location filename="../../../opensnitch/notifications.py" line="108"/> + <source>New outgoing connection</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="334"/> + <source>from this PID</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>popups2</name> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="254"/> + <source><b>Remote</b> process <b>%s</b> running on <b>%s</b> is connecting to <b>%s</b> on %s port %d</source> + <translation type="obsolete">El proceso <b>remoto %s</b> ejecutándose en <b>%s</b> está conectándose a <b>%s</b> en el puerto %s %d</translation> + </message> +</context> +<context> + <name>preferences</name> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="171"/> + <source>Exception saving config: %s</source> + <translation type="obsolete">Error al guarda la configuración: %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="177"/> + <source>Applying configuration on %s ...</source> + <translation type="obsolete">Aplicando configuración en %s ...</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="230"/> + <source>Server address can not be empty</source> + <translation>Адрес сервера не может быть пустым</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="227"/> + <source>Error loading %s configuration</source> + <translation type="obsolete">Error al cargar la configuración %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="477"/> + <source>Configuration applied.</source> + <translation>Конфигурация применена.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="257"/> + <source>Error applying configuration: %s</source> + <translation type="obsolete">Error al aplicar la configuración: %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="321"/> + <source>Exception saving config: {0}</source> + <translation>Конфигурация сохранения исключений: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="418"/> + <source>Applying configuration on {0} ...</source> + <translation>Применение конфигурации к {0}...</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="260"/> + <source>Error loading {0} configuration</source> + <translation>Ошибка при загрузке конфигурации {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="479"/> + <source>Error applying configuration: {0}</source> + <translation>Ошибка применения конфигурации: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="345"/> + <source>Warning</source> + <translation>Предупреждение</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="345"/> + <source>You must select a file for the database<br>or choose "In memory" type.</source> + <translation>Вы должны выбрать файл для базы данных<br>или выбрать тип "В памяти".</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="351"/> + <source>DB type changed</source> + <translation>Тип БД изменен</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="351"/> + <source>Restart the GUI in order effects to take effect</source> + <translation>Перезапустите графический интерфейс, чтобы эффекты вступили в силу</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="509"/> + <source>Hover the mouse over the texts to display the help<br><br>Don't forget to visit the wiki: <a href="{0}">{0}</a></source> + <translation>Наведите указатель мыши на текст, чтобы отобразить справку<br><br>Не забудьте посетить вики: <a href="{0}">{0}</a></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="127"/> + <source>System</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="135"/> + <source>Themes not available. Install qt-material: pip3 install qt-material</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="387"/> + <source>UI theme changed</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="387"/> + <source>Restart the GUI in order to apply the new theme</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>proc_details</name> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="97"/> + <source><b>Error loading process information:</b> <br><br> + +</source> + <translation><b>Ошибка при загрузке информации о процессе:</b> <br><br> + +</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="116"/> + <source><b>Error stopping monitoring process:</b><br><br></source> + <translation><b>Ошибка при остановке мониторинга процесса:</b><br><br></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="156"/> + <source>loading...</source> + <translation>загрузка...</translation> + </message> +</context> +<context> + <name>rules</name> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="164"/> + <source>There're no nodes connected.</source> + <translation>Нет подключенных узлов.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="205"/> + <source>Rule applied.</source> + <translation>Правило применено.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="123"/> + <source>Error applying rule: %s</source> + <translation type="obsolete">Error al aplicar la regla: %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="537"/> + <source>protocol can not be empty, or uncheck it</source> + <translation>протокол не может быть пустым, или снимите галочку</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="551"/> + <source>Protocol regexp error</source> + <translation>Ошибка регулярного выражения протокола</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="555"/> + <source>process path can not be empty</source> + <translation>путь процесса не может быть пустым</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="569"/> + <source>Process path regexp error</source> + <translation>Ошибка регулярного выражения пути процесса</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="573"/> + <source>command line can not be empty</source> + <translation>командная строка не может быть пустой</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="587"/> + <source>Command line regexp error</source> + <translation>Ошибка регулярного выражения командной строки</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="591"/> + <source>Dest port can not be empty</source> + <translation>Порт назначения не может быть пустым</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="605"/> + <source>Dst port regexp error</source> + <translation>Ошибка регулярного выражения порта назначения</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="609"/> + <source>Dest host can not be empty</source> + <translation>Хост назначения не может быть пустым</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="623"/> + <source>Dst host regexp error</source> + <translation>Ошибка регулярного выражения хоста назначения</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="627"/> + <source>Dest IP/Network can not be empty</source> + <translation>Целевой IP/сеть не могут быть пустыми</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="649"/> + <source>Dst IP regexp error</source> + <translation>Ошибка регулярного выражения Целевой IP</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="661"/> + <source>User ID can not be empty</source> + <translation>Укажите идентификатор пользователя</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="675"/> + <source>User ID regexp error</source> + <translation>Ошибка регулярного выражения ID пользователя</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="207"/> + <source>Error applying rule: {0}</source> + <translation>Ошибка применения правила: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="749"/> + <source>Lists field cannot be empty</source> + <translation>Поле списков не может быть пустым</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="751"/> + <source>Lists field must be a directory</source> + <translation>Поле списков должно быть каталогом</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="794"/> + <source><b>Rule not supported</b></source> + <translation><b>Правило не поддерживается</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="439"/> + <source><b>Error loading rule</b></source> + <translation><b>Ошибка загрузки правила</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="181"/> + <source>There's already a rule with this name.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="679"/> + <source>PID field can not be empty</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="693"/> + <source>PID field regexp error</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="781"/> + <source>Select at least one field.</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>stats</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="310"/> + <source>Not running</source> + <translation>Не запущено</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="311"/> + <source>Disabled</source> + <translation>Отключено</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="312"/> + <source>Running</source> + <translation>Запущено</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="412"/> + <source>OpenSnitch Network Statistics</source> + <translation type="obsolete">Eventos de OpenSnitch</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="414"/> + <source>OpenSnitch Network Statistics for</source> + <translation type="obsolete">Eventos de OpenSnitch de</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="899"/> + <source> Your are about to delete this rule. </source> + <translation> Вы собираетесь удалить это правило. </translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1286"/> + <source> Are you sure?</source> + <translation> Вы уверены?</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="583"/> + <source>OpenSnitch Network Statistics {0}</source> + <translation>OpenSnitch статистика сети {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="585"/> + <source>OpenSnitch Network Statistics for {0}</source> + <translation>OpenSnitch статистика сети для {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <translation type="obsolete">Nombre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="294"/> + <source>Address</source> + <translation type="obsolete">Dirección</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="295"/> + <source>Status</source> + <translation type="obsolete">Estado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="296"/> + <source>Hostname</source> + <translation type="obsolete">Hostname</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="297"/> + <source>Version</source> + <translation type="obsolete">Versión</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="298"/> + <source>Rules</source> + <translation type="obsolete">Reglas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Time</source> + <translation type="obsolete">Hora</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Action</source> + <translation type="obsolete">Acción</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Duration</source> + <translation type="obsolete">Duración</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Node</source> + <translation type="obsolete">Nodo</translation> + </message> + <message> + <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="24"/> + <source>Hits</source> + <translation type="unfinished">Total</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>Protocol</source> + <translation type="obsolete">Protocolo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1901"/> + <source>Save as CSV</source> + <translation>Сохранить как CSV</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>Enabled</source> + <translation type="obsolete">Habilitado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="765"/> + <source>Delete</source> + <translation>Удалить</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="948"/> + <source>always</source> + <translation type="obsolete">siempre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="580"/> + <source><b>Error:</b><br><br>{0}</source> + <translation type="obsolete"><b>Error:</b><br><br>{0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="758"/> + <source>Disable</source> + <translation>Отключить</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="760"/> + <source>Enable</source> + <translation>Включить</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="763"/> + <source>Duplicate</source> + <translation>Дублировать</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="764"/> + <source>Edit</source> + <translation>Редактировать</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="918"/> + <source>Rule not found by that name and node</source> + <translation>Правило не найдено по этому имени и узлу</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="948"/> + <source><b>Error:</b><br><br></source> + <comment>{0}</comment> + <translation><b>Ошибка:</b><br><br></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="955"/> + <source>Warning:</source> + <translation>Предупреждение:</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="744"/> + <source>Allow</source> + <translation>Разрешить</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="745"/> + <source>Deny</source> + <translation>Запретить</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="749"/> + <source>Always</source> + <translation>Всегда</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="750"/> + <source>Until reboot</source> + <translation>До перезагрузки</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1286"/> + <source> You are about to delete this rule. </source> + <translation> Вы собираетесь удалить это правило. </translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="306"/> + <source>Process</source> + <translation type="obsolete">Aplicación</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="307"/> + <source>Destination</source> + <translation type="obsolete">Destino</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="308"/> + <source>Rule</source> + <translation type="obsolete">Regla</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="309"/> + <source>UserID</source> + <translation type="obsolete">UserID</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="310"/> + <source>LastConnection</source> + <translation type="obsolete">Última Conexión</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <comment>xxxxx</comment> + <translation type="obsolete">Nombre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Nombre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="294"/> + <source>Address</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Dirección</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="295"/> + <source>Status</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Estado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="296"/> + <source>Hostname</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Hostname</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="297"/> + <source>Version</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Versión</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="298"/> + <source>Rules</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Reglas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Time</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Hora</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Action</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Acción</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Duration</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Duración</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Node</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Nodo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>Enabled</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Habilitado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="304"/> + <source>Hits</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Total</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>Protocol</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Protocolo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="306"/> + <source>Process</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Aplicación</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="307"/> + <source>Destination</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Destino</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="308"/> + <source>Rule</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Regla</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="309"/> + <source>UserID</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">UserID</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="310"/> + <source>LastConnection</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">ÚltimaConexión</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="285"/> + <source>Name</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Имя</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="286"/> + <source>Address</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Адрес</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="287"/> + <source>Status</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Статус</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="288"/> + <source>Hostname</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Имя хоста</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="386"/> + <source>Version</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Версия</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="383"/> + <source>Rules</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Правила</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="292"/> + <source>Time</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Время</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Action</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Действие</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="294"/> + <source>Duration</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Продолжительность</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="295"/> + <source>Node</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Узел</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="296"/> + <source>Enabled</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Включено</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="405"/> + <source>Hits</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Посещаемость</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Protocol</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Протокол</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Process</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Процесс</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Destination</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Назначение</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="306"/> + <source>Rule</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Правило</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="307"/> + <source>UserID</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>ID пользователя</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="308"/> + <source>LastConnection</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Последнее соединение</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Args</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Аргументы</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>DstIP</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>IP назначения</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="304"/> + <source>DstHost</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Хост назначения</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>DstPort</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Порт назначения</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="652"/> + <source>New node connected</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="23"/> + <source>What</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="25"/> + <source>Network name</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="289"/> + <source>Uptime</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">Время работы</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="297"/> + <source>Precedence</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="384"/> + <source>Connections</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">Соединения</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="385"/> + <source>Dropped</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished">Сброшено</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="404"/> + <source>What</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="736"/> + <source>Apply to</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="746"/> + <source>Reject</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>stats_deleterule</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="774"/> + <source> Your are about to delete this rule. </source> + <translation type="obsolete"> Estás a punto de borrar esta regla. </translation> + </message> +</context> +<context> + <name>stats_deleterule2</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="776"/> + <source> Are you sure?</source> + <translation type="obsolete"> ¿Estás seguro?</translation> + </message> +</context> +<context> + <name>stats_disabled</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="74"/> + <source>Disabled</source> + <translation type="obsolete">Deshabilitado</translation> + </message> +</context> +<context> + <name>stats_notrunning</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="73"/> + <source>Not running</source> + <translation type="obsolete">Parado</translation> + </message> +</context> +<context> + <name>stats_running</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="75"/> + <source>Running</source> + <translation type="obsolete">Interceptando</translation> + </message> +</context> +<context> + <name>stats_wintitle</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="409"/> + <source>OpenSnitch Network Statistics</source> + <translation type="obsolete">Eventos de red OpenSnitch</translation> + </message> +</context> +<context> + <name>stats_wintitle2</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="411"/> + <source>OpenSnitch Network Statistics for</source> + <translation type="obsolete">Eventos de OpenSnitch de</translation> + </message> +</context> +</TS> diff --git a/ui/i18n/locales/tr_TR/opensnitch-tr_TR.ts b/ui/i18n/locales/tr_TR/opensnitch-tr_TR.ts new file mode 100644 index 0000000..a8462d7 --- /dev/null +++ b/ui/i18n/locales/tr_TR/opensnitch-tr_TR.ts @@ -0,0 +1,2459 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="tr"> +<context> + <name>Dialog</name> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="34"/> + <source>opensnitch-qt</source> + <translation>opensnitch-qt</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="299"/> + <source>User ID</source> + <translation>Kullanıcı Kimliği</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="333"/> + <source><html><head/><body><p><span style=" font-weight:600;">Executed from</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-weight:600;">Şuradan çalıştırıldı</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="630"/> + <source>TextLabel</source> + <translation>TextLabel</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="426"/> + <source>Source IP</source> + <translation>Kaynak IP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="449"/> + <source>Process ID</source> + <translation>İşlem Kimliği</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="582"/> + <source>Destination IP</source> + <translation>Hedef IP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="605"/> + <source>Dst Port</source> + <translation>Hedef Bağlantı Noktası</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="679"/> + <source>from this executable</source> + <translation>bu programdan</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="684"/> + <source>from this command line</source> + <translation>bu komut satırından</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="689"/> + <source>this destination port</source> + <translation>bu hedef bağlantı noktası</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="694"/> + <source>this user</source> + <translation>bu kullanıcı</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="699"/> + <source>this destination ip</source> + <translation>bu hedef IP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="728"/> + <source>once</source> + <translation>bir kere</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="733"/> + <source>30s</source> + <translation>30sn</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="738"/> + <source>5m</source> + <translation>5dak</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="743"/> + <source>15m</source> + <translation>15dak</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="748"/> + <source>30m</source> + <translation>30dak</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="753"/> + <source>1h</source> + <translation>1sa</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="706"/> + <source>for this session</source> + <translation type="obsolete">durante esta sesión</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="763"/> + <source>forever</source> + <translation>sonsuza kadar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="789"/> + <source>Deny</source> + <translation>Reddet</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="818"/> + <source>Allow</source> + <translation>İzin ver</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="847"/> + <source>+</source> + <translation>+</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="758"/> + <source>until reboot</source> + <translation>yeniden başlatılana kadar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/prompt.ui" line="704"/> + <source>from this PID</source> + <translation>bu işlem kimliğinden</translation> + </message> +</context> +<context> + <name>PreferencesDialog</name> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="14"/> + <source>Preferences</source> + <translation>Tercihler</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="472"/> + <source>UI</source> + <translation>Kullanıcı arayüzü</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="54"/> + <source><html><head/><body><p>This timeout is the countdown you see when a pop-up dialog is shown.</p></body></html></source> + <translation type="obsolete">Este timeout es la cuenta atrás que aparece cuando se muestra una ventana emergente</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="454"/> + <source>Default timeout</source> + <translation>Öntanımlı zaman aşımı</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="331"/> + <source>Pop-up default duration</source> + <translation>Öntanımlı açılır pencere süresi</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="777"/> + <source>Default duration</source> + <translation>Öntanımlı süre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="162"/> + <source>Pop-up default action</source> + <translation type="obsolete">Acción por defecto de la ventana emergente</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="483"/> + <source>Default action</source> + <translation type="obsolete">Acción por defecto</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="314"/> + <source>Default target</source> + <translation>Öntanımlı hedef</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="170"/> + <source>center</source> + <translation>merkez</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="175"/> + <source>top right</source> + <translation>sağ üst</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="180"/> + <source>bottom right</source> + <translation>sağ alt</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="185"/> + <source>top left</source> + <translation>sol üst</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="190"/> + <source>bottom left</source> + <translation>sol alt</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="167"/> + <source>Prompt dialog default position on screen</source> + <translation type="obsolete">Posición por defecto</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="274"/> + <source>by executable</source> + <translation>programa göre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="279"/> + <source>by command line</source> + <translation>komut satırına göre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="284"/> + <source>by destination port</source> + <translation>hedef bağlantı noktasına göre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="289"/> + <source>by destination ip</source> + <translation>hedef IP'ye göre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="294"/> + <source>by user id</source> + <translation>kullanıcı kimliğine göre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="881"/> + <source>once</source> + <translation>bir kere</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="210"/> + <source>30s</source> + <translation>30sn</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="215"/> + <source>5m</source> + <translation>5dak</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="220"/> + <source>15m</source> + <translation>15dak</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="225"/> + <source>30m</source> + <translation>30dak</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="230"/> + <source>1h</source> + <translation>1sa</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="240"/> + <source>for this session</source> + <translation type="obsolete">durante esta sesión</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="240"/> + <source>forever</source> + <translation>sonsuza kadar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="923"/> + <source>deny</source> + <translation>reddet</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="932"/> + <source>allow</source> + <translation>izin ver</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="406"/> + <source>Disable pop-ups, only display an alert</source> + <translation type="obsolete">Açılır pencereleri devre dışı bırak, yalnızca bir uyarı görüntüle</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="734"/> + <source>Nodes</source> + <translation>Düğümler</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="740"/> + <source>Process monitor method</source> + <translation>İşlem izleme yöntemi</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="774"/> + <source><html><head/><body><p>The default duration will take place when there's no UI connected.</p></body></html></source> + <translation><html><head/><body><p>Öntanımlı süre, bağlı bir kullanıcı arayüzü olmadığında gerçekleşecektir.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="899"/> + <source><html><head/><body><p>Address of the node.</p><p>Default: unix:///tmp/osui.sock (unix:// is mandatory if it's a Unix socket)</p><p>It can also be an IP address with the port: 127.0.0.1:50051</p></body></html></source> + <translation><html><head/><body><p>Düğümün adresi.</p><p>Öntanımlı: unix:///tmp/osui.sock (Unix soketi ise unix:// zorunludur)</p><p>Bağlantı noktası ile birlikte bir IP adresi de olabilir: 127.0.0.1:50051</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="902"/> + <source>Address</source> + <translation>Adres</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1042"/> + <source>Default log level</source> + <translation>Öntanımlı günlük kaydı düzeyi</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="950"/> + <source>Version</source> + <translation>Sürüm</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="813"/> + <source><html><head/><body><p>The default action will take place when there's no UI connected.</p></body></html></source> + <translation><html><head/><body><p>Öntanımlı eylem, bağlı bir kullanıcı arayüzü olmadığında gerçekleşecektir.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="757"/> + <source><html><head/><body><p>Log file to write logs.<br/></p><p>/dev/stdout will print logs to the standard output.</p></body></html></source> + <translation><html><head/><body><p>Günlük kayıtlarının yazılacağı günlük dosyası.<br/></p><p>/dev/stdout standart çıktıya yazdıracaktır.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="760"/> + <source>Log file</source> + <translation>Günlük kaydı dosyası</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="578"/> + <source><html><head/><body><p>If checked, opensnitch will prompt you to allow or deny connections that don't have an asocciated PID, due to several reasons.</p><p>The pop-up dialog will only contain information about the network connection.</p></body></html></source> + <translation type="obsolete">Si marcas esta opción, OpenSnitch te preguntará para Aceptar o Denegar conexiones que no tengan un PID asociado por diferentes razones. + +La ventana emergente sólo contendrá información relativa a la conexión. + +Nota: Estas conexiones no tienen por qué indicar que algo sospechoso está sucediendo. Simplemente +es que no hemos descubierto el PID (por ejemplo conexiones que no se originan en la máquina, o paquetes en mal estado).</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="581"/> + <source>Intercept Unknown Connections</source> + <translation type="obsolete">Interceptar conexiones desconocidas</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="832"/> + <source>HostName</source> + <translation>Ana makine adı</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1001"/> + <source>unix:///tmp/osui.sock</source> + <translation>unix:///tmp/osui.sock</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="886"/> + <source>until restart</source> + <translation>yeniden başlatılana kadar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="891"/> + <source>always</source> + <translation>her zaman</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1013"/> + <source>/var/log/opensnitchd.log</source> + <translation>/var/log/opensnitchd.log</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1018"/> + <source>/dev/stdout</source> + <translation>/dev/stdout</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="790"/> + <source>Apply configuration to all nodes</source> + <translation>Yapılandırmayı tüm düğümlere uygula</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1057"/> + <source>Database</source> + <translation>Veri tabanı</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1092"/> + <source>In memory</source> + <translation>Bellekte</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1097"/> + <source>File</source> + <translation>Dosya</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1363"/> + <source>Close</source> + <translation>Kapat</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1374"/> + <source>Apply</source> + <translation>Uygula</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1385"/> + <source>Save</source> + <translation>Kaydet</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="235"/> + <source>until reboot</source> + <translation>yeniden başlatılana kadar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1111"/> + <source>Database type</source> + <translation>Veri tabanı türü</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1118"/> + <source>Select</source> + <translation>Seç</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="83"/> + <source>Pop-ups default options</source> + <translation type="obsolete">Opciones por defecto de las ventanas emergentes</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="367"/> + <source>Pop-ups default position on screen</source> + <translation type="obsolete">Posición en pantalla</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="102"/> + <source><html><head/><body><p>The advanced view allows you to apply more filters on a connection</p><p>when a pop-up appears.</p></body></html></source> + <translation type="obsolete">La vista avanzada permite filtrar conexiones por más parámetros</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="350"/> + <source>Show advanced view by default</source> + <translation>Öntanımlı olarak gelişmiş görünümü göster</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="660"/> + <source>Action</source> + <translation>Eylem</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="366"/> + <source><html><head/><body><p>If checked, the pop-ups will be displayed with the advanced view active.</p></body></html></source> + <translation><html><head/><body><p>İşaretlenirse, açılır pencereler gelişmiş görünüm etkinken görüntülenecektir.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="334"/> + <source>Duration</source> + <translation>Süre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="254"/> + <source><html><head/><body><p>By default when a new pop-up appears, in its simplest form, you'll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).</p><p>With these options, you can choose multiple fields to filter connections for.</p></body></html></source> + <translation><html><head/><body><p>Öntanımlı olarak, yeni bir açılır pencere göründüğünde, en basit haliyle, bağlantıları veya uygulamaları bağlantının bir özelliğine göre (program, bağlantı noktası, IP, vb.) filtreleyebileceksiniz.</p><p>Bu seçeneklerle, bağlantıları filtrelemek için birden fazla alan seçebilirsiniz.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="257"/> + <source>Filter connections also by:</source> + <translation>Bağlantıları şuna göre de filtrele:</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="362"/> + <source>If checked, this field will be checked when a pop-up is displayed</source> + <translation type="obsolete">Si lo seleccionas, este campo se usará para filtrar las conexiones</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="81"/> + <source>User ID</source> + <translation>Kullanıcı kimliği</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="97"/> + <source>Destination port</source> + <translation>Hedef bağlantı noktası</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="113"/> + <source>Destination IP</source> + <translation>Hedef IP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="451"/> + <source><html><head/><body><p>This timeout is the countdown you see when a pop-up dialog is shown.</p><p>If the pop-up is not answered, the default options will be applied.</p></body></html></source> + <translation><html><head/><body><p>Bu zaman aşımı, bir açılır iletişim kutusu gösterildiğinde gördüğünüz geri sayımdır.</p><p>Açılır pencereye yanıt verilmezse, öntanımlı seçenekler uygulanacaktır.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="347"/> + <source>The advanced view allows you to easily select multiple fields to filter connections</source> + <translation>Gelişmiş görünüm, bağlantıları filtrelemek için birden fazla alanı kolayca seçmenize olanak tanır</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="110"/> + <source>If checked, this field will be selected when a pop-up is displayed</source> + <translation>İşaretlenirse, bir açılır pencere görüntülendiğinde bu alan seçilecektir</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="150"/> + <source><html><head/><body><p>Pop-up default action.</p><p>When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.</p><p><br/></p><p>While a pop-up is asking the user to allow or deny a connection:</p><p>1. new outgoing connections are denied.</p><p>2. known connections are allowed or denied based on the rules defined by the user.</p></body></html></source> + <translation><html><head/><body><p>Açılır pencere öntanımlı eylemi.</p><p>Yeni bir giden bağlantı kurulmak üzereyken, bu eylem öntanımlı olarak seçilecektir, bu nedenle zaman aşımı devreye girerse, uygulanacak seçenek budur.</p><p><br/></p><p>Bir açılır pencere kullanıcıdan bir bağlantıya izin vermesini veya reddetmesini isterken:</p><p>1. yeni giden bağlantılar reddedilir.</p><p>2. bilinen bağlantılara kullanıcı tarafından tanımlanan kurallara göre izin verilir veya reddedilir.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="816"/> + <source>Default action when the GUI is disconnected</source> + <translation>GUI bağlantısı kesildiğinde öntanımlı eylem</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="912"/> + <source>Debug invalid connections</source> + <translation>Geçersiz bağlantılarda hata ayıkla</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="39"/> + <source>Pop-ups</source> + <translation>Açılır pencereler</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="64"/> + <source>Default options</source> + <translation>Öntanımlı seçenekler</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="321"/> + <source>Default position on screen</source> + <translation>Ekranda öntanımlı konum</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="713"/> + <source>any temporary rules</source> + <translation>herhangi bir geçici kural</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="478"/> + <source><html><head/><body><p>When this option is selected, the rules of the selected duration won't be added to the list of temporary rules in the GUI.</p><p><br/></p><p>Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</p></body></html></source> + <translation><html><head/><body><p>Bu seçenek seçildiğinde, seçilen sürenin kuralları grafiksel kullanıcı arayüzündeki geçici kurallar listesine eklenmeyecektir.</p><p><br/></p><p>Geçici kurallar hala geçerli olacaktır ve yeni bir bağlantıya izin vermeniz/reddetmeniz istendiğinde bunları kullanabilirsiniz.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="481"/> + <source>Don't save rules of duration</source> + <translation>Süre kurallarını kaydetme</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="463"/> + <source>Show events columns</source> + <translation type="obsolete">Mostrar columnas de la pestaña Eventos</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="596"/> + <source>Time</source> + <translation>Zaman</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="676"/> + <source>Destination</source> + <translation>Hedef</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="644"/> + <source>Protocol</source> + <translation>İletişim kuralı</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="692"/> + <source>Process</source> + <translation>İşlem</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="612"/> + <source>Rule</source> + <translation>Kural</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="628"/> + <source>Node</source> + <translation>Düğüm</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="723"/> + <source><html><head/><body><p>If checked, opensnitch will prompt you to allow or deny connections that don't have an asocciated PID, due to several reasons, mostly due to bad state connections.</p><p>The pop-up dialog will only contain information about the network connection.</p><p>There're some scenarios where these are valid connections though, like when establishing a VPN using wireguard.</p></body></html></source> + <translation type="obsolete"><html><head/><body><p>İşaretlenirse, opensnitch, çoğunlukla kötü durumdaki bağlantılar olmak üzere çeşitli nedenlerden dolayı, atanmış bir işlem kimliğine sahip olmayan bağlantılara izin vermenizi veya reddetmenizi isteyecektir.</p><p>Açılır iletişim kutusu yalnızca ağ bağlantısı hakkında bilgi içerecektir.</p><p>Yine de, örneğin wireguard kullanarak bir VPN kurarken olduğu gibi, bunların geçerli bağlantılar olduğu bazı durumlar vardır.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="584"/> + <source>Events tab columns</source> + <translation>Olaylar sekmesi sütunları</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="299"/> + <source>by PID</source> + <translation>işlem kimliğine göre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="461"/> + <source>Disable pop-ups, only display an notification</source> + <translation>Açılır pencereleri devre dışı bırak, yalnızca bir bildirim görüntüle</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="494"/> + <source>Desktop notifications</source> + <translation>Masaüstü bildirimleri</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="512"/> + <source>Use system notifications</source> + <translation>Sistem bildirimlerini kullan</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="528"/> + <source>Use Qt notifications</source> + <translation>Qt bildirimlerini kullan</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="557"/> + <source>Test</source> + <translation>Test</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="570"/> + <source>System</source> + <translation>Sistem</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="705"/> + <source>Theme</source> + <translation>Tema</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="909"/> + <source><html><head/><body><p>If checked, OpenSnitch will prompt you to allow or deny connections that don't have an associated PID, due to several reasons, mostly due to bad state connections.</p><p>The pop-up dialog will only contain information about the network connection.</p><p>There're some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.</p></body></html></source> + <translation><html><head/><body><p>İşaretlenirse OpenSnitch, çoğunlukla kötü durumdaki bağlantılar olmak üzere çeşitli nedenlerden dolayı ilişkili bir işlem kimliğine sahip olmayan bağlantılara izin vermenizi veya reddetmenizi isteyecektir.</p><p>Açılır iletişim kutusu yalnızca ağ bağlantısı hakkında bilgi içerecektir.</p><p>WireGuard kullanarak bir VPN kurarken olduğu gibi, bunların geçerli bağlantılar olduğu bazı senaryolar vardır.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1196"/> + <source>minutes</source> + <translation>dakika</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1222"/> + <source>Minutes between events purges</source> + <translation>Olay temizlemeleri arasındaki dakikalar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1245"/> + <source>days</source> + <translation>gün</translation> + </message> + <message> + <location filename="../../../opensnitch/res/preferences.ui" line="1255"/> + <source>Maximum days of events to keep</source> + <translation>Saklanacak azami etkinlik günü sayısı</translation> + </message> +</context> +<context> + <name>ProcessDetailsDialog</name> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="14"/> + <source>Process details</source> + <translation>İşlem ayrıntıları</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="61"/> + <source>loading...</source> + <translation>yükleniyor...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="81"/> + <source>CWD: loading...</source> + <translation>CWD: yükleniyor...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="93"/> + <source>mem stats: loading...</source> + <translation>bellek istatistikleri: yükleniyor...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="121"/> + <source>Status</source> + <translation>Durum</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="135"/> + <source>Open files</source> + <translation>Açık dosyalar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="149"/> + <source>I/O Statistics</source> + <translation>G/Ç İstatistikleri</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="163"/> + <source>Memory mapped files</source> + <translation>Bellek eşlemeli dosyalar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="177"/> + <source>Stack</source> + <translation>Yığın</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="191"/> + <source>Environment variables</source> + <translation>Ortam değişkenleri</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="210"/> + <source>Application pids</source> + <translation>Uygulama işlem kimlikleri</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="240"/> + <source>Start or stop monitoring this process</source> + <translation>Bu işlemi izlemeyi başlat veya durdur</translation> + </message> + <message> + <location filename="../../../opensnitch/res/process_details.ui" line="256"/> + <source>Close</source> + <translation>Kapat</translation> + </message> +</context> +<context> + <name>RulesDialog</name> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="20"/> + <source>Rule</source> + <translation>Kural</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="852"/> + <source>Node</source> + <translation>Düğüm</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="875"/> + <source>Apply rule to all nodes</source> + <translation>Kuralı tüm düğümlere uygula</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="254"/> + <source>From this command line</source> + <translation>Bu komut satırından</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="341"/> + <source>From this executable</source> + <translation>Bu programdan</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="56"/> + <source>Action</source> + <translation>Eylem</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="138"/> + <source>/path/to/executable, .*/bin/executable[0-9\.]+$, ...</source> + <translation type="obsolete">/programın/yolu, .*/bin/program[0-9\.]+$, ...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="456"/> + <source>To this IP / Network</source> + <translation>Bu IP'ye / Ağa</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="97"/> + <source>once</source> + <translation>bir kere</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="102"/> + <source>30s</source> + <translation>30sn</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="107"/> + <source>5m</source> + <translation>5dak</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="112"/> + <source>15m</source> + <translation>15dak</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="117"/> + <source>30m</source> + <translation>30dak</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="122"/> + <source>1h</source> + <translation>1sa</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="230"/> + <source>until restart</source> + <translation type="obsolete">hasta reiniciar (el servicio)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="132"/> + <source>always</source> + <translation>her zaman</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="366"/> + <source>To this port</source> + <translation>Bu bağlantı noktasına</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="247"/> + <source>From this user ID</source> + <translation>Bu kullanıcı kimliğinden</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="492"/> + <source>Commas or spaces are not allowed to specify multiple domains. + +Use regular expressions instead: +.*(opensnitch|duckduckgo).com +.*\.google.com + +or a single domain: +www.gnu.org - it'll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ... +gnu.org - it'll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source> + <translation>Birden fazla etki alanı belirtmek için virgül veya boşluk kullanılmasına izin verilmez. + +Bunun yerine düzenli ifadeler kullanın: +.*(opensnitch|duckduckgo).com +.*\.google.com + +veya tek bir etki alanı: +www.gnu.org - yalnızca www.gnu.org ile eşleşir, ftp.gnu.org, www2.gnu.org vb. ile eşleşmez. +gnu.org - yalnızca gnu.org ile eşleşir, www.gnu.org, ftp.gnu.org vb. ile eşleşmez.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="503"/> + <source>www.domain.org, .*\.domain.org</source> + <translation>www.etkialani.org, .*\.etkialani.org</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="396"/> + <source><html><head/><body><p>Only TCP, UDP or UDPLITE are allowed</p><p>You can use regexp, i.e.: ^(TCP|UDP)$</p></body></html></source> + <translation><html><head/><body><p>Yalnızca TCP, UDP veya UDPLITE izin verilir</p><p>Düzenli ifade kullanabilirsiniz, örn: ^(TCP|UDP)$</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="406"/> + <source>TCP</source> + <translation>TCP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="411"/> + <source>UDP</source> + <translation>UDP</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="416"/> + <source>UDPLITE</source> + <translation>UDPLITE</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="421"/> + <source>TCP6</source> + <translation>TCP6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="426"/> + <source>UDP6</source> + <translation>UDP6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="431"/> + <source>UDPLITE6</source> + <translation>UDPLITE6</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="513"/> + <source>You can specify a single IP: +- 192.168.1.1 + +or a regular expression: +- 192\.168\.1\.[0-9]+ + +multiple IPs: +- ^(192\.168\.1\.1|172\.16\.0\.1)$ + +You can also specify a subnet: +- 192.168.1.0/24 + +Note: Commas or spaces are not allowed to separate IPs or networks.</source> + <translation>Tek bir IP belirtebilirsiniz: +- 192.168.1.1 + +veya düzenli bir ifade: +- 192\.168\.1\.[0-9]+ + +birden fazla IP: +- ^(192\.168\.1\.1|172\.16\.0\.1)$ + +Ayrıca bir alt ağ da belirtebilirsiniz: +- 192.168.1.0/24 + +Not: IP veya ağları ayırmak için virgüllere veya boşluklara izin verilmez.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="532"/> + <source>LAN</source> + <translation>LAN</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="537"/> + <source>127.0.0.0/8</source> + <translation>127.0.0.0/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="542"/> + <source>192.168.0.0/24</source> + <translation>192.168.0.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="547"/> + <source>192.168.1.0/24</source> + <translation>192.168.1.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="552"/> + <source>192.168.2.0/24</source> + <translation>192.168.2.0/24</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="557"/> + <source>192.168.0.0/16</source> + <translation>192.168.0.0/16</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="562"/> + <source>169.254.0.0/16</source> + <translation>169.254.0.0/16</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="567"/> + <source>172.16.0.0/12</source> + <translation>172.16.0.0/12</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="572"/> + <source>10.0.0.0/8</source> + <translation>10.0.0.0/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="577"/> + <source>::1/128</source> + <translation>::1/128</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="582"/> + <source>fc00::/7</source> + <translation>fc00::/7</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="587"/> + <source>ff00::/8</source> + <translation>ff00::/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="592"/> + <source>fe80::/10</source> + <translation>fe80::/10</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="597"/> + <source>fd00::/8</source> + <translation>fd00::/8</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="89"/> + <source>Duration</source> + <translation>Süre</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="449"/> + <source>Protocol</source> + <translation>İletişim kuralı</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="373"/> + <source>To this host</source> + <translation>Bu ana makineye</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="151"/> + <source>Deny</source> + <translation>Reddet</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="191"/> + <source>Allow</source> + <translation>İzin ver</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="912"/> + <source>Name</source> + <translation>Ad</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="884"/> + <source>Enable</source> + <translation>Etkinleştir</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="928"/> + <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them. + +000-allow-localhost +001-deny-broadcast +...</source> + <translation>Kurallar alfabetik sıraya göre denetlenir, böylece onları önceliklendirmek için uygun şekilde adlandırabilirsiniz. + +000-localhost-izinver +001-genelyayin-reddet +...</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="935"/> + <source>leave blank to autocreate</source> + <translation>otomatik oluşturmak için boş bırakın</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="891"/> + <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one. + +You must name the rule in such manner that it'll be checked first, because they're checked in alphabetical order. For example: + +[x] Priority - 000-priority-rule +[ ] Priority - 001-less-priority-rule</source> + <translation>İşaretlenirse, bu kural diğer kurallara göre öncelikli olacaktır. Bundan sonra başka hiçbir kural denetlenmeyecektir. + +Alfabetik sıraya göre denetlendikleri için kuralı önce denetlenecek şekilde adlandırmalısınız. Örneğin: + +[x] Öncelik - 000-oncelik-kurali +[ ] Öncelik - 001-daha-az-oncelik-kurali</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="899"/> + <source>Priority rule</source> + <translation>Öncelik kuralı</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="770"/> + <source><html><head/><body><p>By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.<br/></p><p>If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.</p></body></html></source> + <translation><html><head/><body><p>Öntanımlı olarak, kuralların alanı büyük/küçük harfe duyarsızdır, yani bir işlem gOOgle.CoM adresine erişmeye çalışırsa ve .*google.com adresini Reddetmek için bir kuralınız varsa, bağlantı engellenecektir.<br/></p><p>Bu kutuyu işaretlerseniz, filtrelemek istediğiniz dizgeyi (etki alanı, program, komut satırı) tam olarak belirtmeniz gerekir.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="773"/> + <source>Case-sensitive</source> + <translation>Büyük/küçük harfe duyarlı</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="442"/> + <source><html><head/><body><p>You can specify multiple ports using regular expressions:</p><p><br/></p><p>- 53, 80 or 443:</p><p>^(53|80|443)$</p><p><br/></p><p>- 53, 443 or 5551, 5552, 5553, etc:</p><p>^(53|443|555[0-9])$</p></body></html></source> + <translation><html><head/><body><p>Düzenli ifadeler kullanarak birden fazla bağlantı noktası belirtebilirsiniz:</p><p><br/></p> <p> - 53, 80 veya 443:</p><p>^(53|80|443)$</p><p><br/></p> <p> - 53, 443 veya 5551, 5552, 5553, vs:</p><p>^(53|443|555[0-9])$</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="127"/> + <source>until reboot</source> + <translation>yeniden başlatılana kadar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="658"/> + <source>To this list of domains</source> + <translation>Bu etki alanı listesine</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="539"/> + <source><html><head/><body><p>Select a directory with lists of domains to block or allow.</p><p>Put inside that directory files with any extension containing lists of domains.</p><p><br/>The format of each entry of a list is as follow (hosts format):</p><p>127.0.0.1 www.domain.com</p><p>or </p><p>0.0.0.0 www.domain.com</p></body></html></source> + <translation type="obsolete"><html><head/><body><p>Engellenecek veya izin verilecek etki alanlarının listelerini içeren bir dizin seçin.</p><p>Etki alanı listelerini içeren herhangi bir uzantıya sahip dosyaları bu dizinin içine yerleştirin.</p><p><br/>Bir listenin her girdisinin biçimi şu şekildedir (hosts dosyası biçimi):</p><p>127.0.0.1 www.etkialani.com</p><p>veya </p><p>0.0.0.0 www.etkialani.com</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="148"/> + <source>Deny will just discard the connection</source> + <translation>Reddet seçeneği yalnızca bağlantıyı iptal edecektir</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="165"/> + <source>Reject will drop the connection, and kill the socket that initiated it</source> + <translation>Geri çevir seçeneği bağlantıyı kesecek ve onu başlatan soketi sonlandıracaktır</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="168"/> + <source>Reject</source> + <translation>Geri çevir</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="185"/> + <source>Allow will allow the connection</source> + <translation>İzin ver seçeneği bağlantıya izin verecektir</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="221"/> + <source>Applications</source> + <translation>Uygulamalar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="230"/> + <source><html><head/><body><p>The value of this field is always the absolute path to the executable: /path/to/binary<br/></p><p>Examples:</p><p>- Simple: /path/to/binary</p><p>- Multiple paths: ^/usr/lib(64|)/firefox/firefox$</p><p>- Multiple binaries: ^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ </p><p>- Deny/Allow executions from /tmp:</p><p>^/(var/|)tmp/.*$<br/></p><p>For more examples visit the <a href="https://github.com/evilsocket/opensnitch/wiki/Rules-examples">wiki page</a> or ask on the <a href="https://github.com/evilsocket/opensnitch/discussions">Discussion forums</a>.</p></body></html></source> + <translation><html><head/><body><p>Bu alanın değeri her zaman program dosyasının mutlak yoludur: /programın/yolu<br/></p><p>Örnekler:</p><p>- Basit: /programın/yolu</p><p>- Birden çok yol: ^/usr/lib(64|)/firefox/firefox$</p><p>- Birden fazla program: ^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ </p><p>- /tmp'den çalıştırmaları reddet/izin ver:</p><p>^/(var/|)tmp/.*$<br/></p><p>Daha fazla örnek için <a href="https://github.com/evilsocket/opensnitch/wiki/Rules-examples">wiki sayfasını</a> ziyaret edin veya <a href="https://github.com/evilsocket/opensnitch/discussions">Tartışma forumlarında</a> sorun.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="240"/> + <source>Is regular expression</source> + <translation>Düzenli ifadedir</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="264"/> + <source><html><head/><body><p>This field will contain and match the command line that was executed by the user.<br/></p><p>If the user typed the command, only the command will appear:</p><p>telnet 1.2.3.4<br/></p><p>If the user typed the absolute or relative path to the command, that is what will appear:</p><p>/usr/bin/telnet 1.2.3.4</p><p>../../../usr/bin/telnet 1.2.3.4</p></body></html></source> + <translation><html><head/><body><p>Bu alan kullanıcı tarafından çalıştırılan komut satırını içerecek ve eşleşecektir.<br/></p><p>Kullanıcı komutu yazdıysa, yalnızca komut görünecektir:</p><p>telnet 1.2.3.4<br/></p><p>Kullanıcı komutun mutlak veya göreceli yolunu yazdıysa, görünecek olan budur:</p><p>/usr/bin/telnet 1.2.3.4</p><p>../../../usr/bin/telnet 1.2.3.4</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="274"/> + <source>From this PID</source> + <translation>Bu işlem kimliğinden</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="348"/> + <source>is regular expression</source> + <translation>düzenli ifadedir</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="360"/> + <source>Network</source> + <translation>Ağ</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="610"/> + <source>List of domains/IPs</source> + <translation>Etki alanlarının/IP'lerin listesi</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="616"/> + <source>To this list of network ranges</source> + <translation>Bu ağ aralıkları listesine</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="623"/> + <source>To this list of IPs</source> + <translation>Bu IP listesine</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="649"/> + <source><html><head/><body><p>Select a directory with files containing list of IPs to block or allow:</p><p>1.2.3.4.5</p><p>1.2.3.4.6</p><p>.</p><p>etc.</p><p>One IP per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation><html><head/><body><p>Engellenecek veya izin verilecek IP'lerin listesini içeren dosyaların bulunduğu bir dizin seçin:</p><p>1.2.3.4.5</p><p>1.2.3.4.6</p><p>.</p><p>vb.</p><p>Satır başına bir IP. Boş veya # ile başlayan satırlar dikkate alınmaz.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="684"/> + <source><html><head/><body><p>Select a directory with files containing list of network ranges to block or allow:</p><p>1.2.3.0/24</p><p>80.34.56.0/20</p><p>.</p><p>etc.<br/></p><p>One Network Range per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation><html><head/><body><p>Engellenecek veya izin verilecek ağ aralıklarının listesini içeren dosyaların bulunduğu bir dizin seçin:</p><p>1.2.3.0/24</p><p>80.34.56.0/20</p><p>.</p><p>vb.<br/></p><p>Satır başına bir ağ aralığı. Boş veya # ile başlayan satırlar dikkate alınmaz.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="712"/> + <source><html><head/><body><p>Select a directory with lists of domains to block or allow.</p><p>Put inside that directory files with any extension containing lists of domains.</p><p><br/>The format of each entry of a list is as follow (hosts format):</p><p>127.0.0.1 www.domain.com</p><p>or </p><p>0.0.0.0 www.domain.com</p><p>Empty lines or started with # are ignored.</p></body></html></source> + <translation><html><head/><body><p>Engellenecek veya izin verilecek etki alanlarının listelerini içeren bir dizin seçin.</p><p>Etki alanı listelerini içeren herhangi bir uzantıya sahip dosyaları bu dizinin içine yerleştirin.</p><p><br/>Bir listenin her girdisinin biçimi şu şekildedir (hosts dosyası biçimi):</p><p>127.0.0.1 www.etkialani.com</p><p>veya </p><p>0.0.0.0 www.etkialani.com</p><p>Boş veya # ile başlayan satırlar dikkate alınmaz.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="727"/> + <source>To this list of domains +(regular expressions)</source> + <translation>Bu etki alanları listesine +(düzenli ifadeler)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="754"/> + <source><html><head/><body><p>Select a directory with files containing regular expressions of domains to block or allow:</p><p>.*\.example\.com</p><p>You can also use a domain as is: &quot;example.com&quot; , and it'll match whatever.example.com, whatever.example.com.localdomain, etc.</p><p>One domain per line. Empty lines or started with # are ignored.</p></body></html></source> + <translation><html><head/><body><p>Engellenecek veya izin verilecek etki alanlarının düzenli ifadelerini içeren dosyaları içeren bir dizin seçin:</p><p>.*\.ornek\.com</p><p>Bir etki alanını olduğu gibi de kullanabilirsiniz: &quot;ornek.com&quot;, bu herhangibirsey.ornek.com, herhangibirsey.ornek.com.localdomain, vb. ile eşleşecektir.</p><p>Satır başına bir etki alanı. Boş veya # ile başlayan satırlar dikkate alınmaz.</p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/ruleseditor.ui" line="764"/> + <source>More</source> + <translation>Daha fazla</translation> + </message> +</context> +<context> + <name>StatsDialog</name> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="34"/> + <source>OpenSnitch Network Statistics</source> + <translation>OpenSnitch Ağ İstatistikleri</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="284"/> + <source>Save to CSV.</source> + <translation>CSV olarak kaydet.</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="294"/> + <source>Ctrl+S</source> + <translation>Ctrl+S</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="330"/> + <source>Create a new rule</source> + <translation>Yeni bir kural oluştur</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="360"/> + <source><html><head/><body><p><span style=" font-size:11pt; font-weight:600;">hostname - 192.168.1.1</span></p></body></html></source> + <translation><html><head/><body><p><span style=" font-size:11pt; font-weight:600;">ana makine adı - 192.168.1.1</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="399"/> + <source>Status</source> + <translation>Durum</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1627"/> + <source>-</source> + <translation>-</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="437"/> + <source>Start or Stop interception</source> + <translation>Araya girmeyi başlat veya durdur</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="482"/> + <source>Events</source> + <translation>Olaylar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="94"/> + <source>Filter</source> + <translation>Filtrele</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="107"/> + <source>Allow</source> + <translation>İzin ver</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="116"/> + <source>Deny</source> + <translation>Reddet</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="143"/> + <source>Ex.: firefox</source> + <translation>Örn: firefox</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="199"/> + <source>50</source> + <translation>50</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="204"/> + <source>100</source> + <translation>100</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="209"/> + <source>200</source> + <translation>200</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="214"/> + <source>300</source> + <translation>300</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="724"/> + <source>Nodes</source> + <translation>Düğümler</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="554"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on the Addr column to view details of a node)</span></p></body></html></source> + <translation type="obsolete"><html><head/><body><p><span style=" font-size:7pt;">(bir düğümün ayrıntılarını görüntülemek için Adres sütununa çift tıklayın)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1531"/> + <source>Rules</source> + <translation>Kurallar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="833"/> + <source>enable</source> + <translation>etkinleştir</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="684"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on the Name column to view details of a rule)</span></p></body></html></source> + <translation type="obsolete">(doble click en la columna Nombre para ver los detalles)</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="692"/> + <source>search rule name</source> + <translation type="obsolete">kural adı ara</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="680"/> + <source>Application rules</source> + <translation>Uygulama kuralları</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="782"/> + <source>Permanent</source> + <translation>Kalıcı</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="791"/> + <source>Temporary</source> + <translation>Geçici</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="895"/> + <source>Hosts</source> + <translation>Ana makineler</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1364"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click to view details of an item)</span></p></body></html></source> + <translation type="obsolete"><html><head/><body><p><span style=" font-size:7pt;">(bir ögenin ayrıntılarını görüntülemek için çift tıklayın)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="982"/> + <source>Applications</source> + <translation>Uygulamalar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1089"/> + <source>Addresses</source> + <translation>Adresler</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1176"/> + <source>Ports</source> + <translation>Bağlantı noktaları</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1260"/> + <source>Users</source> + <translation>Kullanıcılar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1366"/> + <source>Connections</source> + <translation>Bağlantılar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1421"/> + <source>Dropped</source> + <translation>Bırakıldı</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1476"/> + <source>Uptime</source> + <translation>Çalışma süresi</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1601"/> + <source>Version</source> + <translation>Sürüm</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="227"/> + <source>Delete all intercepted events</source> + <translation>Araya girilen tüm olayları sil</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="840"/> + <source>Edit rule</source> + <translation>Kuralı düzenle</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="854"/> + <source>Delete rule</source> + <translation>Kuralı sil</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="926"/> + <source>Delete all intercepted hosts</source> + <translation type="obsolete">Araya girilen tüm ana makineleri sil</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1051"/> + <source>Delete all intercepted applications</source> + <translation type="obsolete">Araya girilen tüm uygulamaları sil</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1159"/> + <source>Delete all intercepted addresses</source> + <translation type="obsolete">Araya girilen tüm adresleri sil</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1261"/> + <source>Delete all intercepted ports</source> + <translation type="obsolete">Araya girilen tüm bağlantı noktalarını sil</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="1371"/> + <source>Delete all intercepted users</source> + <translation type="obsolete">Araya girilen tüm kullanıcıları sil</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="699"/> + <source><html><head/><body><p><span style=" font-size:7pt;">(double click on a row to view details of a rule)</span></p></body></html></source> + <translation type="obsolete"><html><head/><body><p><span style=" font-size:7pt;">(bir kuralın ayrıntılarını görüntülemek için bir satıra çift tıklayın)</span></p></body></html></translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="665"/> + <source>Delete connections that matched this rule</source> + <translation type="obsolete">Bu kuralla eşleşen bağlantıları sil</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="773"/> + <source>All applications</source> + <translation>Tüm uygulamalar</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="125"/> + <source>Reject</source> + <translation>Geri çevir</translation> + </message> + <message> + <location filename="../../../opensnitch/res/stats.ui" line="177"/> + <source>0</source> + <translation>0</translation> + </message> +</context> +<context> + <name>contextual_menu</name> + <message> + <location filename="../../../opensnitch/service.py" line="43"/> + <source>Statistics</source> + <translation>İstatistikler</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="46"/> + <source>Help</source> + <translation>Yardım</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="47"/> + <source>Close</source> + <translation>Kapat</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="44"/> + <source>Enable</source> + <translation>Etkinleştir</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="45"/> + <source>Disable</source> + <translation>Devre dışı bırak</translation> + </message> +</context> +<context> + <name>menu_close</name> + <message> + <location filename="../../../opensnitch/service.py" line="131"/> + <source>Close</source> + <translation type="obsolete">Cerrar</translation> + </message> +</context> +<context> + <name>menu_help</name> + <message> + <location filename="../../../opensnitch/service.py" line="126"/> + <source>Help</source> + <translation type="obsolete">Ayuda</translation> + </message> +</context> +<context> + <name>menu_statistics</name> + <message> + <location filename="../../../opensnitch/service.py" line="120"/> + <source>Statistics</source> + <translation type="obsolete">Eventos</translation> + </message> +</context> +<context> + <name>notifications</name> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="547"/> + <source>System notifications are not available, you need to install python3-notify2.</source> + <translation>Sistem bildirimleri kullanılamıyor, python3-notify2 kurmanız gerekiyor.</translation> + </message> +</context> +<context> + <name>popups</name> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="91"/> + <source>Allow</source> + <translation>İzin ver</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="92"/> + <source>Deny</source> + <translation>Reddet</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="54"/> + <source>forever</source> + <translation>sonsuza kadar</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="285"/> + <source>Outgoing connection</source> + <translation>Giden bağlantı</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="290"/> + <source>Process launched from:</source> + <translation>İşlem şuradan başlatıldı:</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="325"/> + <source>from this command line</source> + <translation>bu komut satırından</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="321"/> + <source>from this executable</source> + <translation>bu programdan</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="208"/> + <source>Unknown process</source> + <translation type="obsolete">Proceso no encontrado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="52"/> + <source>until reboot</source> + <translation>yeniden başlatılana kadar</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="327"/> + <source>to port {0}</source> + <translation>{0} bağlantı noktasına</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="222"/> + <source><b>%s</b> is connecting to <b>%s</b> on %s port %d</source> + <translation type="obsolete"><b>%s</b> está conectándose a <b>%s</b> en el puerto %s %d</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="228"/> + <source><b>Remote</b> process <b>%s</b> running on <b>%s</b> is connecting to <b>%s</b> on %s port %d</source> + <translation type="obsolete">El proceso <b>remoto %s</b> ejecutándose en <b>%s</b> está conectándose a <b>%s</b> en el puerto %s %d</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="390"/> + <source>to {0}</source> + <translation>{0} hedefine</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="330"/> + <source>from user {0}</source> + <translation>{0} kullanıcısından</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="347"/> + <source>to {0}.*</source> + <translation>{0}.* hedefine</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="400"/> + <source>to *.{0}</source> + <translation>*.{0} hedefine</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="378"/> + <source>to *{0}</source> + <translation type="obsolete">*{0} hedefine</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="434"/> + <source><b>Remote</b> process %s running on <b>%s</b></source> + <translation>%s <b>uzak</b> işlemi, <b>%s</b> üzerinde çalışıyor</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="438"/> + <source>is connecting to <b>%s</b> on %s port %d</source> + <translation><b>%s</b> hedefine bağlanıyor, %s bağlantı noktası %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="444"/> + <source>is attempting to resolve <b>%s</b> via %s, %s port %d</source> + <translation><b>%s</b> çözümlemeye çalışıyor, %s aracılığıyla, %s bağlantı noktası %d</translation> + </message> + <message> + <location filename="../../../opensnitch/notifications.py" line="108"/> + <source>New outgoing connection</source> + <translation>Yeni giden bağlantı</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="334"/> + <source>from this PID</source> + <translation>bu işlem kimliğinden</translation> + </message> +</context> +<context> + <name>popups2</name> + <message> + <location filename="../../../opensnitch/dialogs/prompt.py" line="254"/> + <source><b>Remote</b> process <b>%s</b> running on <b>%s</b> is connecting to <b>%s</b> on %s port %d</source> + <translation type="obsolete">El proceso <b>remoto %s</b> ejecutándose en <b>%s</b> está conectándose a <b>%s</b> en el puerto %s %d</translation> + </message> +</context> +<context> + <name>preferences</name> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="171"/> + <source>Exception saving config: %s</source> + <translation type="obsolete">Error al guarda la configuración: %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="177"/> + <source>Applying configuration on %s ...</source> + <translation type="obsolete">Aplicando configuración en %s ...</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="230"/> + <source>Server address can not be empty</source> + <translation>Sunucu adresi boş olamaz</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="227"/> + <source>Error loading %s configuration</source> + <translation type="obsolete">Error al cargar la configuración %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="477"/> + <source>Configuration applied.</source> + <translation>Yapılandırma uygulandı.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="257"/> + <source>Error applying configuration: %s</source> + <translation type="obsolete">Error al aplicar la configuración: %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="321"/> + <source>Exception saving config: {0}</source> + <translation>Yapılandırma kaydedilirken istisna oluştu: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="418"/> + <source>Applying configuration on {0} ...</source> + <translation>{0} üzerinde yapılandırma uygulanıyor...</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="260"/> + <source>Error loading {0} configuration</source> + <translation>{0} yapılandırması yüklenirken hata oluştu</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="479"/> + <source>Error applying configuration: {0}</source> + <translation>Yapılandırma uygulanırken hata oluştu: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="345"/> + <source>Warning</source> + <translation>Uyarı</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="345"/> + <source>You must select a file for the database<br>or choose "In memory" type.</source> + <translation>Veri tabanı için bir dosya seçmelisiniz<br>veya "Bellekte" türünü seçmelisiniz.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="351"/> + <source>DB type changed</source> + <translation>V.T. türü değişti</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="351"/> + <source>Restart the GUI in order effects to take effect</source> + <translation>Değişikliklerin etkili olabilmesi için grafiksel arayüzü yeniden başlatın</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="509"/> + <source>Hover the mouse over the texts to display the help<br><br>Don't forget to visit the wiki: <a href="{0}">{0}</a></source> + <translation>Yardımı görüntülemek için fareyi metinlerin üzerine getirin<br><br>Wiki sayfasını ziyaret etmeyi unutmayın: <a href="{0}">{0}</a></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="127"/> + <source>System</source> + <translation>Sistem</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="135"/> + <source>Themes not available. Install qt-material: pip3 install qt-material</source> + <translation>Temalar kullanılamıyor. qt-material kurun: pip3 install qt-material</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="387"/> + <source>UI theme changed</source> + <translation>Kullanıcı arayüzü teması değiştirildi</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/preferences.py" line="387"/> + <source>Restart the GUI in order to apply the new theme</source> + <translation>Yeni temayı uygulamak için grafiksel arayüzü yeniden başlatın</translation> + </message> +</context> +<context> + <name>proc_details</name> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="97"/> + <source><b>Error loading process information:</b> <br><br> + +</source> + <translation><b>İşlem bilgileri yüklenirken hata oluştu:</b> <br><br> + +</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="116"/> + <source><b>Error stopping monitoring process:</b><br><br></source> + <translation><b>İşlemin izlenmesi durdurulurken hata oluştu:</b><br><br></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/processdetails.py" line="156"/> + <source>loading...</source> + <translation>yükleniyor...</translation> + </message> +</context> +<context> + <name>rules</name> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="164"/> + <source>There're no nodes connected.</source> + <translation>Bağlı düğüm yok.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="205"/> + <source>Rule applied.</source> + <translation>Kural uygulandı.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="123"/> + <source>Error applying rule: %s</source> + <translation type="obsolete">Error al aplicar la regla: %s</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="537"/> + <source>protocol can not be empty, or uncheck it</source> + <translation>iletişim kuralı boş olamaz veya işaretini kaldırın</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="551"/> + <source>Protocol regexp error</source> + <translation>İletişim kuralı düzenli ifade hatası</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="555"/> + <source>process path can not be empty</source> + <translation>işlem yolu boş olamaz</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="569"/> + <source>Process path regexp error</source> + <translation>İşlem yolu düzenli ifade hatası</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="573"/> + <source>command line can not be empty</source> + <translation>komut satırı boş olamaz</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="587"/> + <source>Command line regexp error</source> + <translation>Komut satırı düzenli ifade hatası</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="591"/> + <source>Dest port can not be empty</source> + <translation>Hedef bağlantı noktası boş olamaz</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="605"/> + <source>Dst port regexp error</source> + <translation>Hedef bağlantı noktası düzenli ifade hatası</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="609"/> + <source>Dest host can not be empty</source> + <translation>Hedef ana makine boş olamaz</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="623"/> + <source>Dst host regexp error</source> + <translation>Hedef ana makine düzenli ifade hatası</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="627"/> + <source>Dest IP/Network can not be empty</source> + <translation>Hedef IP/Ağ boş olamaz</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="649"/> + <source>Dst IP regexp error</source> + <translation>Hedef IP düzenli ifade hatası</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="661"/> + <source>User ID can not be empty</source> + <translation>Kullanıcı kimliği boş olamaz</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="675"/> + <source>User ID regexp error</source> + <translation>Kullanıcı kimliği düzenli ifade hatası</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="207"/> + <source>Error applying rule: {0}</source> + <translation>Kural uygulanırken hata oluştu: {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="749"/> + <source>Lists field cannot be empty</source> + <translation>Listeler alanı boş olamaz</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="751"/> + <source>Lists field must be a directory</source> + <translation>Listeler alanı bir dizin olmalıdır</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="794"/> + <source><b>Rule not supported</b></source> + <translation><b>Kural desteklenmiyor</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="439"/> + <source><b>Error loading rule</b></source> + <translation><b>Kural yüklenirken hata oluştu</b></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="181"/> + <source>There's already a rule with this name.</source> + <translation>Bu ada sahip bir kural zaten var.</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="679"/> + <source>PID field can not be empty</source> + <translation>İşlem kimliği alanı boş olamaz</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="693"/> + <source>PID field regexp error</source> + <translation>İşlem kimliği alanı düzenli ifade hatası</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="781"/> + <source>Select at least one field.</source> + <translation>En az bir alan seçin.</translation> + </message> +</context> +<context> + <name>stats</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="310"/> + <source>Not running</source> + <translation>Çalışmıyor</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="311"/> + <source>Disabled</source> + <translation>Devre dışı</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="312"/> + <source>Running</source> + <translation>Çalışıyor</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="412"/> + <source>OpenSnitch Network Statistics</source> + <translation type="obsolete">Eventos de OpenSnitch</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="414"/> + <source>OpenSnitch Network Statistics for</source> + <translation type="obsolete">Eventos de OpenSnitch de</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="899"/> + <source> Your are about to delete this rule. </source> + <translation> Bu kuralı silmek üzeresiniz. </translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1286"/> + <source> Are you sure?</source> + <translation> Emin misiniz?</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="583"/> + <source>OpenSnitch Network Statistics {0}</source> + <translation>OpenSnitch Ağ İstatistikleri {0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="585"/> + <source>OpenSnitch Network Statistics for {0}</source> + <translation>{0} için OpenSnitch Ağ İstatistikleri</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <translation type="obsolete">Nombre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="294"/> + <source>Address</source> + <translation type="obsolete">Dirección</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="295"/> + <source>Status</source> + <translation type="obsolete">Estado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="296"/> + <source>Hostname</source> + <translation type="obsolete">Hostname</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="297"/> + <source>Version</source> + <translation type="obsolete">Versión</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="298"/> + <source>Rules</source> + <translation type="obsolete">Reglas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Time</source> + <translation type="obsolete">Hora</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Action</source> + <translation type="obsolete">Acción</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Duration</source> + <translation type="obsolete">Duración</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Node</source> + <translation type="obsolete">Nodo</translation> + </message> + <message> + <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="24"/> + <source>Hits</source> + <translation>Kullanıldı</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>Protocol</source> + <translation type="obsolete">Protocolo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1901"/> + <source>Save as CSV</source> + <translation>CSV olarak kaydet</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>Enabled</source> + <translation type="obsolete">Habilitado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="765"/> + <source>Delete</source> + <translation>Sil</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="948"/> + <source>always</source> + <translation type="obsolete">siempre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="580"/> + <source><b>Error:</b><br><br>{0}</source> + <translation type="obsolete"><b>Error:</b><br><br>{0}</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="758"/> + <source>Disable</source> + <translation>Devre dışı bırak</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="760"/> + <source>Enable</source> + <translation>Etkinleştir</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="763"/> + <source>Duplicate</source> + <translation>Çoğalt</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="764"/> + <source>Edit</source> + <translation>Düzenle</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="918"/> + <source>Rule not found by that name and node</source> + <translation>Bu ada ve düğüme göre kural bulunamadı</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="948"/> + <source><b>Error:</b><br><br></source> + <comment>{0}</comment> + <translation><b>Hata:</b><br><br></translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="955"/> + <source>Warning:</source> + <translation>Uyarı:</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="744"/> + <source>Allow</source> + <translation>İzin ver</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="745"/> + <source>Deny</source> + <translation>Reddet</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="749"/> + <source>Always</source> + <translation>Her zaman</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="750"/> + <source>Until reboot</source> + <translation>Yeniden başlatılana kadar</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="1286"/> + <source> You are about to delete this rule. </source> + <translation> Bu kuralı silmek üzeresiniz. </translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="306"/> + <source>Process</source> + <translation type="obsolete">Aplicación</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="307"/> + <source>Destination</source> + <translation type="obsolete">Destino</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="308"/> + <source>Rule</source> + <translation type="obsolete">Regla</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="309"/> + <source>UserID</source> + <translation type="obsolete">UserID</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="310"/> + <source>LastConnection</source> + <translation type="obsolete">Última Conexión</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <comment>xxxxx</comment> + <translation type="obsolete">Nombre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Name</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Nombre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="294"/> + <source>Address</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Dirección</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="295"/> + <source>Status</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Estado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="296"/> + <source>Hostname</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Hostname</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="297"/> + <source>Version</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Versión</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="298"/> + <source>Rules</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Reglas</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Time</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Hora</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Action</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Acción</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Duration</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Duración</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Node</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Nodo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>Enabled</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Habilitado</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="304"/> + <source>Hits</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Total</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>Protocol</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Protocolo</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="306"/> + <source>Process</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Aplicación</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="307"/> + <source>Destination</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Destino</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="308"/> + <source>Rule</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">Regla</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="309"/> + <source>UserID</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">UserID</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="310"/> + <source>LastConnection</source> + <comment>This is a word, without spaces</comment> + <translation type="obsolete">ÚltimaConexión</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="285"/> + <source>Name</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Ad</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="286"/> + <source>Address</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Adres</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="287"/> + <source>Status</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Durum</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="288"/> + <source>Hostname</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Ana makine adı</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="386"/> + <source>Version</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Sürüm</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="383"/> + <source>Rules</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Kurallar</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="292"/> + <source>Time</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Zaman</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="293"/> + <source>Action</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Eylem</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="294"/> + <source>Duration</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Süre</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="295"/> + <source>Node</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Düğüm</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="296"/> + <source>Enabled</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Etkin</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="405"/> + <source>Hits</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Kullanıldı</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="299"/> + <source>Protocol</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>İletişim kuralı</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="300"/> + <source>Process</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>İşlem</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="302"/> + <source>Destination</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Hedef</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="306"/> + <source>Rule</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Kural</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="307"/> + <source>UserID</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>KullanıcıKimliği</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="308"/> + <source>LastConnection</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>SonBağlantı</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="301"/> + <source>Args</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Argümanlar</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="303"/> + <source>DstIP</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>HedefIP</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="304"/> + <source>DstHost</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>HedefAnaMakine</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="305"/> + <source>DstPort</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>HedefBağlantıNoktası</translation> + </message> + <message> + <location filename="../../../opensnitch/service.py" line="652"/> + <source>New node connected</source> + <translation>Yeni düğüm bağlandı</translation> + </message> + <message> + <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="23"/> + <source>What</source> + <translation>Ne</translation> + </message> + <message> + <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="25"/> + <source>Network name</source> + <translation>Ağ adı</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="289"/> + <source>Uptime</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Çalışma süresi</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="297"/> + <source>Precedence</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Öncelik</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="384"/> + <source>Connections</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Bağlantılar</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="385"/> + <source>Dropped</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Bırakıldı</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="404"/> + <source>What</source> + <comment>This is a word, without spaces and symbols.</comment> + <translation>Ne</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="736"/> + <source>Apply to</source> + <translation>Uygula</translation> + </message> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="746"/> + <source>Reject</source> + <translation>Geri çevir</translation> + </message> +</context> +<context> + <name>stats_deleterule</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="774"/> + <source> Your are about to delete this rule. </source> + <translation type="obsolete"> Estás a punto de borrar esta regla. </translation> + </message> +</context> +<context> + <name>stats_deleterule2</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="776"/> + <source> Are you sure?</source> + <translation type="obsolete"> ¿Estás seguro?</translation> + </message> +</context> +<context> + <name>stats_disabled</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="74"/> + <source>Disabled</source> + <translation type="obsolete">Deshabilitado</translation> + </message> +</context> +<context> + <name>stats_notrunning</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="73"/> + <source>Not running</source> + <translation type="obsolete">Parado</translation> + </message> +</context> +<context> + <name>stats_running</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="75"/> + <source>Running</source> + <translation type="obsolete">Interceptando</translation> + </message> +</context> +<context> + <name>stats_wintitle</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="409"/> + <source>OpenSnitch Network Statistics</source> + <translation type="obsolete">Eventos de red OpenSnitch</translation> + </message> +</context> +<context> + <name>stats_wintitle2</name> + <message> + <location filename="../../../opensnitch/dialogs/stats.py" line="411"/> + <source>OpenSnitch Network Statistics for</source> + <translation type="obsolete">Eventos de OpenSnitch de</translation> + </message> +</context> +</TS> diff --git a/ui/i18n/opensnitch_i18n.pro b/ui/i18n/opensnitch_i18n.pro new file mode 100644 index 0000000..1f7092a --- /dev/null +++ b/ui/i18n/opensnitch_i18n.pro @@ -0,0 +1,33 @@ +#TEMPLATE = app +#TARGET = ts +#INCLUDEPATH += opensnitch + + +# Input +SOURCES += ../opensnitch/service.py \ + ../opensnitch/notifications.py \ + ../opensnitch/customwidgets/addresstablemodel.py \ + ../opensnitch/customwidgets/main.py \ + ../opensnitch/dialogs/prompt.py \ + ../opensnitch/dialogs/preferences.py \ + ../opensnitch/dialogs/ruleseditor.py \ + ../opensnitch/dialogs/processdetails.py \ + ../opensnitch/dialogs/stats.py + +FORMS += ../opensnitch/res/prompt.ui \ + ../opensnitch/res/ruleseditor.ui \ + ../opensnitch/res/preferences.ui \ + ../opensnitch/res/process_details.ui \ + ../opensnitch/res/stats.ui +TRANSLATIONS += locales/de_DE/opensnitch-de_DE.ts \ + locales/es_ES/opensnitch-es_ES.ts \ + locales/eu_ES/opensnitch-eu_ES.ts \ + locales/hu_HU/opensnitch-hu_HU.ts \ + locales/ja_JP/opensnitch-ja_JP.ts \ + locales/pt_BR/opensnitch-pt_BR.ts \ + locales/ro_RO/opensnitch-ro_RO.ts \ + locales/fr_FR/opensnitch-fr_FR.ts \ + locales/lt_LT/opensnitch-lt_LT.ts \ + locales/tr_TR/opensnitch-tr_TR.ts \ + locales/ru_RU/opensnitch-ru_RU.ts \ + locales/nb_NO/opensnitch-nb_NO.ts diff --git a/ui/opensnitch-ui.spec b/ui/opensnitch-ui.spec new file mode 100644 index 0000000..43a1d8e --- /dev/null +++ b/ui/opensnitch-ui.spec @@ -0,0 +1,110 @@ +%define name opensnitch-ui +%define version 1.5.8 +%define unmangled_version 1.5.8 +%define release 1 +%define __python python3 +%define desktop_file opensnitch_ui.desktop + +Summary: Prompt service and UI for the opensnitch application firewall. +Name: %{name} +Version: %{version} +Release: %{release} +Source0: %{name}-%{unmangled_version}.tar.gz +License: GPL-3.0 +Group: Development/Libraries +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot +Prefix: %{_prefix} +BuildArch: noarch +Vendor: Simone "evilsocket" Margaritelli <evilsocket@protonmail.com> +Url: https://github.com/evilsocket/opensnitch +Requires: python3, python3-pip, (python3-pyinotify or python3-inotify), python3-qt5, python3-notify2 +Recommends: (python3-slugify or python3-python-slugify), python3-protobuf >= 3.0 + +# avoid to depend on a particular python version +%global __requires_exclude ^python\\(abi\\) = 3\\..$ + +%description +GUI for the opensnitch application firewall +opensnitch-ui is a GUI for opensnitch written in Python. +It allows the user to view live outgoing connections, as well as search +to make connections. +. +The user can decide if block the outgoing connection based on properties of +the connection: by port, by uid, by dst ip, by program or a combination +of them. +. +These rules can last forever, until the app restart or just one time. + +%prep +%setup -n %{name}-%{unmangled_version} -n %{name}-%{unmangled_version} + +%post + +if [ $1 -ge 1 ]; then + for i in $(ls /home) + do + if grep /home/$i /etc/passwd &>/dev/null; then + path=/home/$i/.config/autostart/ + if [ ! -d $path ]; then + mkdir -p $path + fi + if [ -f /usr/share/applications/%{desktop_file} ];then + ln -s /usr/share/applications/%{desktop_file} $path 2>/dev/null || true + else + echo "No desktop file: %{desktop_file}" + fi + fi + done + + gtk-update-icon-cache /usr/share/icons/hicolor/ || true +fi + +if [ $1 -eq 1 ]; then + echo -e "\n You need to install 2 more packages: + unicode_slugify and grpcio-tools. + + pip3 install grpcio-tools + pip3 install unicode_slugify + " +fi + +%postun +if [ $1 -eq 0 ]; then + for i in $(ls /home) + do + if grep /home/$i /etc/passwd &>/dev/null; then + path=/home/$i/.config/autostart/%{desktop_file} + if [ -h $path -o -f $path ]; then + rm -f $path + else + echo "No desktop file for this user: $path" + fi + fi + done + + pkill -15 opensnitch-ui 2>/dev/null || true + + echo "" + echo " Remember to uninstall grpcio-tools and unicode_slugify if you don't" + echo " need them anymore:" + echo " pip3 uninstall unicode_slugify" + echo " pip3 uninstall grpcio-tools" + echo "" +fi + + +%build +cd i18n; make; cd .. +cp -r i18n/locales/ opensnitch/i18n +pyrcc5 -o opensnitch/resources_rc.py opensnitch/res/resources.qrc +sed -i 's/^import ui_pb2/from . import ui_pb2/' opensnitch/ui_pb2* +python3 setup.py build + +%install +python3 setup.py install --install-lib=/usr/lib/python3/dist-packages/ --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --prefix=/usr --record=INSTALLED_FILES + +%clean +rm -rf $RPM_BUILD_ROOT + +%files -f INSTALLED_FILES +%defattr(-,root,root) diff --git a/ui/opensnitch/__init__.py b/ui/opensnitch/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ui/opensnitch/config.py b/ui/opensnitch/config.py new file mode 100644 index 0000000..46f965d --- /dev/null +++ b/ui/opensnitch/config.py @@ -0,0 +1,159 @@ +from PyQt5 import QtCore +from opensnitch.database import Database + +class Config: + __instance = None + + HELP_URL = "https://github.com/evilsocket/opensnitch/wiki/" + HELP_RULES_URL = "https://github.com/evilsocket/opensnitch/wiki/Rules" + HELP_CONFIG_URL = "https://github.com/evilsocket/opensnitch/wiki/Configurations" + + RULE_TYPE_LIST = "list" + RULE_TYPE_LISTS = "lists" + RULE_TYPE_SIMPLE = "simple" + RULE_TYPE_REGEXP = "regexp" + RULE_TYPE_NETWORK = "network" + RulesTypes = (RULE_TYPE_LIST, RULE_TYPE_LISTS, RULE_TYPE_SIMPLE, RULE_TYPE_REGEXP, RULE_TYPE_NETWORK) + + RULES_DURATION_FILTER = () + + DEFAULT_DURATION_IDX = 6 # until restart + DEFAULT_TARGET_PROCESS = 0 + ACTION_DENY_IDX = 0 + ACTION_ALLOW_IDX = 1 + + # don't translate + ACTION_ALLOW = "allow" + ACTION_DENY = "deny" + ACTION_REJECT = "reject" + DURATION_UNTIL_RESTART = "until restart" + DURATION_ALWAYS = "always" + DURATION_ONCE = "once" + DURATION_1h = "1h" + DURATION_30m = "30m" + DURATION_15m = "15m" + DURATION_5m = "5m" + DURATION_30s = "30s" + + POPUP_CENTER = 0 + POPUP_TOP_RIGHT = 1 + POPUP_BOTTOM_RIGHT = 2 + POPUP_TOP_LEFT = 3 + POPUP_BOTTOM_LEFT = 4 + + DEFAULT_THEME = "global/theme" + DEFAULT_DISABLE_POPUPS = "global/disable_popups" + DEFAULT_TIMEOUT_KEY = "global/default_timeout" + DEFAULT_ACTION_KEY = "global/default_action" + DEFAULT_DURATION_KEY = "global/default_duration" + DEFAULT_TARGET_KEY = "global/default_target" + DEFAULT_IGNORE_RULES = "global/default_ignore_rules" + DEFAULT_IGNORE_TEMPORARY_RULES = "global/default_ignore_temporary_rules" + DEFAULT_POPUP_POSITION = "global/default_popup_position" + DEFAULT_POPUP_ADVANCED = "global/default_popup_advanced" + DEFAULT_POPUP_ADVANCED_DSTIP = "global/default_popup_advanced_dstip" + DEFAULT_POPUP_ADVANCED_DSTPORT = "global/default_popup_advanced_dstport" + DEFAULT_POPUP_ADVANCED_UID = "global/default_popup_advanced_uid" + DEFAULT_SERVER_ADDR = "global/server_address" + DEFAULT_DB_TYPE_KEY = "database/type" + DEFAULT_DB_FILE_KEY = "database/file" + DEFAULT_DB_PURGE_OLDEST = "database/purge_oldest" + DEFAULT_DB_MAX_DAYS = "database/max_days" + DEFAULT_DB_PURGE_INTERVAL = "database/purge_interval" + + DEFAULT_TIMEOUT = 30 + + NOTIFICATIONS_ENABLED = "notifications/enabled" + NOTIFICATIONS_TYPE = "notifications/type" + NOTIFICATION_TYPE_SYSTEM = 0 + NOTIFICATION_TYPE_QT = 1 + + STATS_GEOMETRY = "statsDialog/geometry" + STATS_LAST_TAB = "statsDialog/last_tab" + STATS_FILTER_TEXT = "statsDialog/general_filter_text" + STATS_FILTER_ACTION = "statsDialog/general_filter_action" + STATS_LIMIT_RESULTS = "statsDialog/general_limit_results" + STATS_SHOW_COLUMNS = "statsDialog/show_columns" + STATS_NODES_COL_STATE = "statsDialog/nodes_columns_state" + STATS_GENERAL_COL_STATE = "statsDialog/general_columns_state" + STATS_GENERAL_FILTER_TEXT = "statsDialog/" + STATS_GENERAL_FILTER_ACTION = "statsDialog/" + STATS_RULES_COL_STATE = "statsDialog/rules_columns_state" + STATS_RULES_TREE_EXPANDED_0 = "statsDialog/rules_tree_0_expanded" + STATS_RULES_TREE_EXPANDED_1 = "statsDialog/rules_tree_1_expanded" + STATS_RULES_SPLITTER_POS = "statsDialog/rules_splitter_pos" + STATS_VIEW_COL_STATE = "statsDialog/view_columns_state" + STATS_VIEW_DETAILS_COL_STATE = "statsDialog/view_details_columns_state" + # don't translate + + @staticmethod + def init(): + Config.__instance = Config() + return Config.__instance + + @staticmethod + def get(): + if Config.__instance == None: + Config._instance = Config() + return Config.__instance + + def __init__(self): + self.settings = QtCore.QSettings("opensnitch", "settings") + + if self.settings.value(self.DEFAULT_TIMEOUT_KEY) == None: + self.setSettings(self.DEFAULT_TIMEOUT_KEY, self.DEFAULT_TIMEOUT) + if self.settings.value(self.DEFAULT_ACTION_KEY) == None: + self.setSettings(self.DEFAULT_ACTION_KEY, self.ACTION_ALLOW) + if self.settings.value(self.DEFAULT_DURATION_KEY) == None: + self.setSettings(self.DEFAULT_DURATION_KEY, self.DEFAULT_DURATION_IDX) + if self.settings.value(self.DEFAULT_TARGET_KEY) == None: + self.setSettings(self.DEFAULT_TARGET_KEY, self.DEFAULT_TARGET_PROCESS) + if self.settings.value(self.DEFAULT_DB_TYPE_KEY) == None: + self.setSettings(self.DEFAULT_DB_TYPE_KEY, Database.DB_TYPE_MEMORY) + self.setSettings(self.DEFAULT_DB_FILE_KEY, Database.DB_IN_MEMORY) + + self.setRulesDurationFilter( + self.getBool(self.DEFAULT_IGNORE_RULES), + self.getInt(self.DEFAULT_IGNORE_TEMPORARY_RULES) + ) + + def reload(self): + self.settings = QtCore.QSettings("opensnitch", "settings") + + def hasKey(self, key): + return self.settings.contains(key) + + def setSettings(self, path, value): + self.settings.setValue(path, value) + self.settings.sync() + + def getSettings(self, path): + return self.settings.value(path) + + def getBool(self, path, default_value=False): + return self.settings.value(path, type=bool, defaultValue=default_value) + + def getInt(self, path, default_value=0): + try: + return self.settings.value(path, type=int, defaultValue=default_value) + except Exception: + return default_value + + def getDefaultAction(self): + _default_action = self.getInt(self.DEFAULT_ACTION_KEY) + if _default_action == self.ACTION_ALLOW_IDX: + return self.ACTION_ALLOW + else: + return self.ACTION_DENY + + def setRulesDurationFilter(self, ignore_temporary_rules=False, temp_rules=1): + if ignore_temporary_rules: + if temp_rules == 1: + Config.RULES_DURATION_FILTER = (Config.DURATION_ONCE) + elif temp_rules == 0: + Config.RULES_DURATION_FILTER = ( + Config.DURATION_ONCE, Config.DURATION_30s, Config.DURATION_5m, + Config.DURATION_15m, Config.DURATION_30m, Config.DURATION_1h, + Config.DURATION_UNTIL_RESTART) + else: + Config.RULES_DURATION_FILTER = () diff --git a/ui/opensnitch/customwidgets/__init__.py b/ui/opensnitch/customwidgets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ui/opensnitch/customwidgets/addresstablemodel.py b/ui/opensnitch/customwidgets/addresstablemodel.py new file mode 100644 index 0000000..abc47f8 --- /dev/null +++ b/ui/opensnitch/customwidgets/addresstablemodel.py @@ -0,0 +1,69 @@ + +from PyQt5 import Qt, QtCore +from PyQt5.QtGui import QColor, QStandardItemModel, QStandardItem +from PyQt5.QtSql import QSqlQueryModel, QSqlQuery, QSql +from PyQt5.QtWidgets import QTableView, QAbstractSlider +from PyQt5.QtCore import QItemSelectionModel, pyqtSignal, QEvent +import time +import math + +from opensnitch.utils import AsnDB +from opensnitch.customwidgets.generictableview import GenericTableModel +from PyQt5.QtCore import QCoreApplication as QC + +class AddressTableModel(GenericTableModel): + + def __init__(self, tableName, headerLabels): + super().__init__(tableName, headerLabels) + self.asndb = AsnDB.instance() + self.reconfigureColumns() + + def reconfigureColumns(self): + self.headerLabels = [] + self.setHorizontalHeaderLabels(self.headerLabels) + self.headerLabels.append(QC.translate("stats", "What", "")) + self.headerLabels.append(QC.translate("stats", "Hits", "")) + self.headerLabels.append(QC.translate("stats", "Network name", "")) + self.setHorizontalHeaderLabels(self.headerLabels) + self.setColumnCount(len(self.headerLabels)) + self.lastColumnCount = len(self.headerLabels) + + def setQuery(self, q, db): + self.origQueryStr = q + self.db = db + + if self.prevQueryStr != self.origQueryStr: + self.realQuery = QSqlQuery(q, db) + + self.realQuery.exec_() + self.realQuery.last() + + queryRows = max(0, self.realQuery.at()+1) + self.totalRowCount = queryRows + self.setRowCount(self.totalRowCount) + + queryColumns = self.realQuery.record().count() + if self.asndb.is_available() and queryColumns < 3: + self.reconfigureColumns() + else: + # update view's columns + if queryColumns != self.lastColumnCount: + self.setModelColumns(queryColumns) + + self.prevQueryStr = self.origQueryStr + self.rowCountChanged.emit() + + def fillRows(self, q, upperBound, force=False): + super().fillRows(q, upperBound, force) + + if self.asndb.is_available() == True and self.columnCount() <= 3: + for n, col in enumerate(self.items): + try: + if len(col) < 2: + continue + col[2] = self.asndb.get_asn(col[0]) + except Exception as e: + col[2] = "" + finally: + self.items[n] = col + self.lastItems = self.items diff --git a/ui/opensnitch/customwidgets/generictableview.py b/ui/opensnitch/customwidgets/generictableview.py new file mode 100644 index 0000000..e8b4477 --- /dev/null +++ b/ui/opensnitch/customwidgets/generictableview.py @@ -0,0 +1,323 @@ + +from PyQt5 import Qt, QtCore +from PyQt5.QtGui import QColor, QStandardItemModel, QStandardItem +from PyQt5.QtSql import QSqlQueryModel, QSqlQuery, QSql +from PyQt5.QtWidgets import QTableView, QAbstractSlider +from PyQt5.QtCore import QItemSelectionModel, pyqtSignal, QEvent +import time +import math + +from PyQt5.QtCore import QCoreApplication as QC + +class GenericTableModel(QStandardItemModel): + rowCountChanged = pyqtSignal() + + db = None + tableName = "" + # total row count which must de displayed in the view + totalRowCount = 0 + # + lastColumnCount = 0 + + # original query string before we modify it + origQueryStr = QSqlQuery() + # previous original query string; used to check if the query has changed + prevQueryStr = '' + # modified query object + realQuery = QSqlQuery() + + items = [] + lastItems = [] + + def __init__(self, tableName, headerLabels): + self.tableName = tableName + self.headerLabels = headerLabels + self.lastColumnCount = len(self.headerLabels) + QStandardItemModel.__init__(self, 0, self.lastColumnCount) + self.setHorizontalHeaderLabels(self.headerLabels) + + #Some QSqlQueryModel methods must be mimiced so that this class can serve as a drop-in replacement + #mimic QSqlQueryModel.query() + def query(self): + return self + + #mimic QSqlQueryModel.query().lastQuery() + def lastQuery(self): + return self.origQueryStr + + #mimic QSqlQueryModel.query().lastError() + def lastError(self): + return self.realQuery.lastError() + + #mimic QSqlQueryModel.clear() + def clear(self): + pass + + def data(self, index, role=QtCore.Qt.DisplayRole): + if role == QtCore.Qt.DisplayRole: + items_count = len(self.items) + if index.isValid() and items_count > 0 and index.row() < items_count: + return self.items[index.row()][index.column()] + return QStandardItemModel.data(self, index, role) + + # set columns based on query's fields + def setModelColumns(self, newColumns): + self.headerLabels = [] + self.removeColumns(0, self.lastColumnCount) + self.setHorizontalHeaderLabels(self.headerLabels) + for col in range(0, newColumns): + self.headerLabels.append(self.realQuery.record().fieldName(col)) + self.lastColumnCount = newColumns + self.setHorizontalHeaderLabels(self.headerLabels) + self.setColumnCount(len(self.headerLabels)) + + def setQuery(self, q, db): + self.origQueryStr = q + self.db = db + #print("q:", q) + + if self.prevQueryStr != self.origQueryStr: + self.realQuery = QSqlQuery(q, db) + + self.realQuery.exec_() + self.realQuery.last() + + queryRows = max(0, self.realQuery.at()+1) + self.totalRowCount = queryRows + self.setRowCount(self.totalRowCount) + + # update view's columns + queryColumns = self.realQuery.record().count() + if queryColumns != self.lastColumnCount: + self.setModelColumns(queryColumns) + + self.prevQueryStr = self.origQueryStr + self.rowCountChanged.emit() + + def nextRecord(self, offset): + cur_pos = self.realQuery.at() + q.seek(max(cur_pos, cur_pos+offset)) + + def prevRecord(self, offset): + cur_pos = self.realQuery.at() + q.seek(min(cur_pos, cur_pos-offset)) + + # refresh the viewport with data from the db. + def refreshViewport(self, scrollValue, maxRowsInViewport, force=False): + # set records position to last, in order to get correctly the number of + # rows. + self.realQuery.last() + rowsFound = max(0, self.realQuery.at()+1) + if scrollValue == 0 or self.realQuery.at() == QSql.BeforeFirstRow: + self.realQuery.seek(QSql.BeforeFirstRow) + elif self.realQuery.at() == QSql.AfterLastRow: + self.realQuery.seek(rowsFound - maxRowsInViewport) + else: + self.realQuery.seek(min(scrollValue-1, self.realQuery.at())) + + upperBound = min(maxRowsInViewport, rowsFound) + self.setRowCount(self.totalRowCount) + + # only visible rows will be filled with data, and only if we're not + # updating the viewport already. + if upperBound > 0 or self.realQuery.at() < 0: + self.fillRows(self.realQuery, upperBound, force) + + def fillRows(self, q, upperBound, force=False): + rowsLabels = [] + self.setVerticalHeaderLabels(rowsLabels) + + self.items = [] + cols = [] + self.blockSignals(True) + #don't trigger setItem's signals for each cell, instead emit dataChanged for all cells + for x in range(0, upperBound): + q.next() + if q.at() < 0: + # if we don't set query to a valid record here, it gets stucked + # forever at -2/-1. + q.seek(upperBound) + break + rowsLabels.append(str(q.at()+1)) + cols = [] + for col in range(0, len(self.headerLabels)): + cols.append(str(q.value(col))) + + self.items.append(cols) + self.blockSignals(False) + + self.setVerticalHeaderLabels(rowsLabels) + if self.lastItems != self.items or force == True: + self.dataChanged.emit(self.createIndex(0,0), self.createIndex(upperBound, len(self.headerLabels))) + self.lastItems = self.items + del cols + + def dumpRows(self): + rows = [] + q = QSqlQuery(self.db) + q.exec(self.origQueryStr) + q.seek(QSql.BeforeFirstRow) + while True: + q.next() + if q.at() == QSql.AfterLastRow: + break + row = [] + for col in range(0, len(self.headerLabels)): + row.append(q.value(col)) + rows.append(row) + return rows + +class GenericTableView(QTableView): + # how many rows can potentially be displayed in viewport + # the actual number of rows currently displayed may be less than this + maxRowsInViewport = 0 + vScrollBar = None + + def __init__(self, parent): + QTableView.__init__(self, parent) + #eventFilter to catch key up/down events and wheel events + self.installEventFilter(self) + self.verticalHeader().setVisible(True) + self.horizontalHeader().setDefaultAlignment(QtCore.Qt.AlignCenter) + self.horizontalHeader().setStretchLastSection(True) + #the built-in vertical scrollBar of this view is always off + self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + + def setVerticalScrollBar(self, vScrollBar): + self.vScrollBar = vScrollBar + self.vScrollBar.valueChanged.connect(self.onValueChanged) + self.vScrollBar.setVisible(False) + + def setModel(self, model): + super().setModel(model) + model.rowCountChanged.connect(self.onRowCountChanged) + model.rowsInserted.connect(self.onRowsInsertedOrRemoved) + model.rowsRemoved.connect(self.onRowsInsertedOrRemoved) + self.horizontalHeader().sortIndicatorChanged.disconnect() + self.setSortingEnabled(False) + + # model().rowCount() is always <= self.maxRowsInViewport + # stretch the bottom row; we don't want partial-height rows at the bottom + # this will only trigger if rowCount value was changed + def onRowsInsertedOrRemoved(self, parent, start, end): + if self.model().rowCount() == self.maxRowsInViewport: + self.verticalHeader().setStretchLastSection(True) + else: + self.verticalHeader().setStretchLastSection(False) + + def resizeEvent(self, event): + super().resizeEvent(event) + #refresh the viewport data based on new geometry + self.refresh() + + def refresh(self): + self.calculateRowsInViewport() + self.model().setRowCount(min(self.maxRowsInViewport, self.model().totalRowCount)) + self.model().refreshViewport(self.vScrollBar.value(), self.maxRowsInViewport, force=True) + + def calculateRowsInViewport(self): + rowHeight = self.verticalHeader().defaultSectionSize() + columnSize = self.horizontalHeader().defaultSectionSize() + # we don't want partial-height rows in viewport, hence .floor() + self.maxRowsInViewport = math.floor(self.viewport().height() / rowHeight)+1 + + def onValueChanged(self, vSBNewValue): + self.model().refreshViewport(vSBNewValue, self.maxRowsInViewport, force=True) + + def onRowCountChanged(self): + scrollBar = self.vScrollBar + + totalCount = self.model().totalRowCount + scrollBar.setVisible(True if totalCount > self.maxRowsInViewport else False) + + scrollBar.setMinimum(0) + # we need to substract the displayed rows to the total rows, to scroll + # down correctly. + scrollBar.setMaximum(max(0, totalCount - self.maxRowsInViewport+1)) + + self.model().refreshViewport(scrollBar.value(), self.maxRowsInViewport) + + def getCurrentIndex(self): + return self.selectionModel().currentIndex().internalId() + + def selectItem(self, _data, _column): + """Select a row based on the data displayed on the given column. + """ + items = self.model().findItems(_data, column=_column) + if len(items) > 0: + self.selectionModel().setCurrentIndex( + items[0].index(), + QItemSelectionModel.Rows | QItemSelectionModel.SelectCurrent + ) + + def _selectLastRow(self): + internalId = self.getCurrentIndex() + idx = self.model().createIndex(self.maxRowsInViewport-2, 0, internalId) + self.selectionModel().setCurrentIndex( + idx, QItemSelectionModel.Rows | QItemSelectionModel.SelectCurrent + ) + + def _selectRow(self, pos): + internalId = self.getCurrentIndex() + self.selectionModel().setCurrentIndex( + self.model().createIndex(pos, 1, internalId), QItemSelectionModel.Rows | QItemSelectionModel.SelectCurrent + ) + + def onKeyUp(self): + if self.selectionModel().currentIndex().row() == 0: + self.vScrollBar.setValue(max(0, self.vScrollBar.value() - 1)) + + def onKeyDown(self): + if self.vScrollBar.isVisible() == False: + return + + if self.selectionModel().currentIndex().row() >= self.maxRowsInViewport-2: + self.vScrollBar.setValue(self.vScrollBar.value() + 1) + self._selectLastRow() + + def onKeyHome(self): + self.vScrollBar.setValue(0) + self.selectionModel().setCurrentIndex(self.model().createIndex(0, 0), QItemSelectionModel.Rows | QItemSelectionModel.SelectCurrent) + + def onKeyEnd(self): + self.vScrollBar.setValue(self.vScrollBar.maximum()) + self._selectLastRow() + + def onKeyPageUp(self): + newValue = max(0, self.vScrollBar.value() - self.maxRowsInViewport) + self.vScrollBar.setValue(newValue) + + def onKeyPageDown(self): + if self.vScrollBar.isVisible() == False: + return + + newValue = self.vScrollBar.value() + (self.maxRowsInViewport-2) + if newValue >= self.model().rowCount(): + self._selectLastRow() + return + + if newValue < self.model().rowCount(): + self.vScrollBar.setValue(newValue) + self._selectRow(0) + + def eventFilter(self, obj, event): + if event.type() == QEvent.KeyPress: + # FIXME: setValue() does not update the scrollbars correctly in + # some pyqt versions. + if event.key() == QtCore.Qt.Key_Up: + self.onKeyUp() + elif event.key() == QtCore.Qt.Key_Down: + self.onKeyDown() + elif event.key() == QtCore.Qt.Key_Home: + self.onKeyHome() + elif event.key() == QtCore.Qt.Key_End: + self.onKeyEnd() + elif event.key() == QtCore.Qt.Key_PageUp: + self.onKeyPageUp() + elif event.key() == QtCore.Qt.Key_PageDown: + self.onKeyPageDown() + elif event.type() == QEvent.Wheel: + self.vScrollBar.wheelEvent(event) + return True + #return False + return super(GenericTableView, self).eventFilter(obj, event) diff --git a/ui/opensnitch/customwidgets/main.py b/ui/opensnitch/customwidgets/main.py new file mode 100644 index 0000000..a6f86bd --- /dev/null +++ b/ui/opensnitch/customwidgets/main.py @@ -0,0 +1,535 @@ +from PyQt5 import Qt, QtCore +from PyQt5.QtGui import QColor, QStandardItemModel, QStandardItem +from PyQt5.QtSql import QSqlQueryModel, QSqlQuery, QSql +from PyQt5.QtWidgets import QTableView +from PyQt5.QtCore import QItemSelectionModel, pyqtSignal, QEvent +import time +import math + +from PyQt5.QtCore import QCoreApplication as QC + +# PyQt5 >= v5.15.8 (#821) +if hasattr(Qt, "QItemDelegate"): + from PyQt5.Qt import QItemDelegate, QStyle +else: + from PyQt5.QtWidgets import QItemDelegate, QStyle + +class ColorizedDelegate(QItemDelegate): + def __init__(self, parent=None, *args, config=None): + QItemDelegate.__init__(self, parent, *args) + self._config = config + self._alignment = QtCore.Qt.AlignLeft | QtCore.Qt.AlignHCenter + + def paint(self, painter, option, index): + if not index.isValid(): + return super().paint(painter, option, index) + + nocolor=True + + value = index.data(QtCore.Qt.DisplayRole) + for _, what in enumerate(self._config): + if what == value: + nocolor=False + painter.save() + painter.setPen(self._config[what]) + if 'alignment' in self._config: + self._alignment = self._config['alignment'] + + if option.state & QStyle.State_Selected: + painter.setBrush(painter.brush()) + painter.setPen(painter.pen()) + painter.drawText(option.rect, self._alignment, value) + painter.restore() + + if nocolor == True: + super().paint(painter, option, index) + +class ColorizedQSqlQueryModel(QSqlQueryModel): + """ + model=CustomQSqlQueryModel( + modelData= + { + 'colorize': + {'offline': (QColor(QtCore.Qt.red), 2)}, + 'alignment': { Qt.AlignLeft, 2 } + } + ) + """ + RED = QColor(QtCore.Qt.red) + GREEN = QColor(QtCore.Qt.green) + + def __init__(self, modelData={}): + QSqlQueryModel.__init__(self) + self._model_data = modelData + + def data(self, index, role=QtCore.Qt.DisplayRole): + if not index.isValid(): + return QSqlQueryModel.data(self, index, role) + + column = index.column() + row = index.row() + + if role == QtCore.Qt.TextAlignmentRole: + return QtCore.Qt.AlignCenter + if role == QtCore.Qt.TextColorRole: + for _, what in enumerate(self._model_data): + d = QSqlQueryModel.data(self, self.index(row, self._model_data[what][1]), QtCore.Qt.DisplayRole) + if column == self._model_data[what][1] and what in d: + return self._model_data[what][0] + + return QSqlQueryModel.data(self, index, role) + +class ConnectionsTableModel(QStandardItemModel): + rowCountChanged = pyqtSignal() + + #max rowid in the db; starts with 1, not with 0 + maxRowId = 0 + #previous total number of rows in the db when the filter was applied + prevFiltRowCount = 0 + #total number of rows in the db when the filter was not applied + prevNormRowCount = 0 + #total row count which must de displayed in the view + totalRowCount = 0 + #new rows which must be added to the top of the rows displayed in the view + prependedRowCount = 0 + + db = None + #original query string before we modify it + origQueryStr = QSqlQuery() + #modified query object + realQuery = QSqlQuery() + #previous original query string; used to check if the query has changed + prevQueryStr = '' + #whether or not the original query has a filter (a WHERE condition) + isQueryFilter = False + limit = None + + #a map for fast lookup or rows when filter is enabled + #contains ranges of rowids and count of filter hits + #range format {'from': <rowid>, 'to': <rowid>, 'hits':<int>} + #including the 'from' rowid up to but NOT including the 'to' rowid + map = [] + rangeSize = 1000 + #all unique/distinct values for each column will be stored here + distinct = {'time':[], 'process':[], 'dst_host':[], 'dst_ip':[], 'dst_port':[], 'rule':[], 'node':[], 'protocol':[]} + #what was the last rowid\time when the distinct value were updates + distinctLastRowId = 0 + distinctLastUpdateTime = time.time() + + def __init__(self): + self.headerLabels = [ + QC.translate("stats", "Time", "This is a word, without spaces and symbols.").replace(" ", ""), + QC.translate("stats", "Node", "This is a word, without spaces and symbols.").replace(" ", ""), + QC.translate("stats", "Action", "This is a word, without spaces and symbols.").replace(" ", ""), + QC.translate("stats", "Destination", "This is a word, without spaces and symbols.").replace(" ", ""), + QC.translate("stats", "Protocol", "This is a word, without spaces and symbols.").replace(" ", ""), + QC.translate("stats", "Process", "This is a word, without spaces and symbols.").replace(" ", ""), + QC.translate("stats", "Rule", "This is a word, without spaces and symbols.").replace(" ", ""), + ] + QStandardItemModel.__init__(self, 0, len(self.headerLabels)) + self.setHorizontalHeaderLabels(self.headerLabels) + + #Some QSqlQueryModel methods must be mimiced so that this class can serve as a drop-in replacement + #mimic QSqlQueryModel.query() + def query(self): + return self + + #mimic QSqlQueryModel.query().lastQuery() + def lastQuery(self): + return self.origQueryStr + + #mimic QSqlQueryModel.query().lastError() + def lastError(self): + return self.realQuery.lastError() + + #mimic QSqlQueryModel.clear() + def clear(self): + pass + + def setQuery(self, q, db): + self.origQueryStr = q + self.db = db + maxRowIdQuery = QSqlQuery(db) + maxRowIdQuery.setForwardOnly(True) + maxRowIdQuery.exec("SELECT MAX(rowid) FROM connections") + maxRowIdQuery.first() + value = maxRowIdQuery.value(0) + self.maxRowId = 0 if value == '' else int(value) + self.updateDistinctIfNeeded() + self.limit = int(q.split(' ')[-1]) if q.split(' ')[-2] == 'LIMIT' else None + self.isQueryFilter = True if ("LIKE '%" in q and "LIKE '% %'" not in q) or 'Action = "' in q else False + + self.realQuery = QSqlQuery(db) + isTotalRowCountChanged = False + isQueryChanged = False + if self.prevQueryStr != q: + isQueryChanged = True + if self.isQueryFilter: + if isQueryChanged: + self.buildMap() + largestRowIdInMap = self.map[0]['from'] + newRowsCount = self.maxRowId - largestRowIdInMap + self.prependedRowCount = 0 + + if newRowsCount > 0: + starttime = time.time() + self.realQuery.setForwardOnly(True) + for offset in range(0, newRowsCount, self.rangeSize): + lowerBound = largestRowIdInMap + offset + upperBound = min(lowerBound + self.rangeSize, self.maxRowId) + part1, part2 = q.split('ORDER') + qStr = part1 + 'AND rowid>'+ str(lowerBound) + ' AND rowid<=' + str(upperBound) + ' ORDER' + part2 + self.realQuery.exec(qStr) + self.realQuery.last() + rowsInRange = max(0, self.realQuery.at()+1) + if self.map[0]['from'] - self.map[0]['to'] < self.rangeSize: + #consolidate with the previous range; we don't want many small ranges + self.map[0]['from'] = upperBound + self.map[0]['hits'] += rowsInRange + else: + self.map.insert(0, {'from':upperBound, 'to':lowerBound, 'hits':rowsInRange}) + self.prependedRowCount += rowsInRange + if time.time() - starttime > 0.5: + #dont freeze the UI when fetching too many recent rows + break + + self.totalRowCount = 0 + for i in self.map: + self.totalRowCount += i['hits'] + if self.totalRowCount != self.prevFiltRowCount: + isTotalRowCountChanged = True + self.prevFiltRowCount = self.totalRowCount + else: #self.isQueryFilter == False + self.prependedRowCount = self.maxRowId - self.prevNormRowCount + self.totalRowCount = self.maxRowId + if self.totalRowCount != self.prevNormRowCount: + isTotalRowCountChanged = True + self.prevNormRowCount = self.totalRowCount + + self.prevQueryStr = self.origQueryStr + if isTotalRowCountChanged or self.prependedRowCount > 0 or isQueryChanged: + self.rowCountChanged.emit() + + #fill self.map with data + def buildMap(self): + self.map = [] + q = QSqlQuery(self.db) + q.setForwardOnly(True) + self.updateDistinctIfNeeded(True) + filterStr = self.getFilterStr() + actionStr = self.getActionStr() + #we only want to know the count of matching rows + qStr = "SELECT COUNT(*) from connections WHERE (rowid> :lowerBound AND rowid<= :upperBound)" + if actionStr: + qStr += ' AND ' + actionStr + matchStr = self.getMatch(filterStr) if filterStr else None + if matchStr: + qStr += ' AND ' + matchStr + qStr += ' LIMIT ' + str(self.limit) if self.limit else '' + q.prepare(qStr) + + totalRows = 0 + for offset in range(self.maxRowId, -1, -self.rangeSize): + upperBound = offset + lowerBound = max(0, upperBound - self.rangeSize) + if (not filterStr and actionStr) or (filterStr and matchStr): + #either 1) only action was present or 2) filter which has a match (with or without action) + q.bindValue(":lowerBound", str(lowerBound)) + q.bindValue(":upperBound", str(upperBound)) + q.exec_() + q.first() + rowsInRange = int(q.value(0)) + else: + rowsInRange = 0 + totalRows += rowsInRange + self.map.append({'from':upperBound, 'to':lowerBound, 'hits':rowsInRange}) + if self.limit and totalRows >= self.limit: + break + + #periodically keep track of all distinct values for each column + #this is needed in order to build efficient queries when the filter is applied + def updateDistinctIfNeeded(self, force=False): + if (not force and (time.time() - self.distinctLastUpdateTime) < 10) or self.maxRowId == self.distinctLastRowId: + return + if (self.maxRowId < self.distinctLastRowId): + #the db has been cleared, re-init the values + self.distinctLastRowId = 0 + self.distinct = {'time':[], 'process':[], 'dst_host':[], 'dst_ip':[], 'dst_port':[], 'rule':[], 'node':[], 'protocol':[]} + q = QSqlQuery(self.db) + q.setForwardOnly(True) + for column in self.distinct.keys(): + q.exec('SELECT DISTINCT ' + column + ' FROM connections WHERE rowid>' + + str(self.distinctLastRowId) + ' AND rowid<=' + str(self.maxRowId)) + while q.next(): + if q.value(0) not in self.distinct[column]: + self.distinct[column].append(q.value(0)) + self.distinctLastRowId =self.maxRowId + self.distinctLastUpdateTime = time.time() + + #refresh the viewport with data from the db + #"value" is vertical scrollbar's value + def refreshViewport(self, value, maxRowsInViewport): + q = QSqlQuery(self.db) + #sequential number of topmost/bottommost rows in viewport (numbering starts from the bottom with 1 not with 0) + botRowNo = max(1, self.totalRowCount - (value + maxRowsInViewport-1)) + topRowNo = min(botRowNo + maxRowsInViewport-1, self.totalRowCount) + + if not self.isQueryFilter: + part1, part2 = self.origQueryStr.split('ORDER') + qStr = part1 + 'WHERE rowid>='+ str(botRowNo) + ' AND rowid<=' + str(topRowNo) + ' ORDER' + part2 + else: + self.updateDistinctIfNeeded(True) + #replace query part between WHERE and ORDER + qStr = self.origQueryStr.split('WHERE')[0] + ' WHERE ' + actionStr = self.getActionStr() + if actionStr: + qStr += actionStr + " AND " + #find inside the map the range(s) in which top and bottom rows are located + total, offsetInRange, botRowFound, topRowFound = 0, None, False, False + ranges = [{'from':0, 'to':0, 'hits':0}] + for i in reversed(self.map): + if total + i['hits'] >= botRowNo: + botRowFound = True + if total + i['hits'] >= topRowNo: + topRowFound = True + if botRowFound and i['hits'] > 0: + if i['to'] == ranges[-1]['from']: + #merge two adjacent ranges + ranges[-1]['from'] = i['from'] + ranges[-1]['hits'] += i['hits'] + else: + ranges.append(i.copy()) + if topRowFound: + offsetInRange = i['hits'] - (topRowNo - total) + break + total += i['hits'] + + rangeStr = '' + if len(ranges) > 0: + rangeStr = '(' + for r in ranges: + rangeStr += '(rowid>' + str(r['to']) + ' AND rowid<=' + str(r['from']) + ') OR ' + rangeStr = rangeStr[:-3] #remove trailing 'OR ' + rangeStr += ') AND ' + qStr += rangeStr + + filterStr = self.getFilterStr() + matchStr = self.getMatch(filterStr) if filterStr else None + if matchStr: + qStr += matchStr + " AND " + qStr = qStr[:-4] #remove trailing ' AND' + qStr += ' ORDER '+ self.origQueryStr.split('ORDER')[1] + + q.exec(qStr) + q.last() + rowsFound = max(0, q.at()+1) + if not self.isQueryFilter: + q.seek(QSql.BeforeFirstRow) + else: + #position the db cursor on topRowNo + q.seek(QSql.BeforeFirstRow if offsetInRange == 0 else offsetInRange-1) + upperBound = min(maxRowsInViewport, rowsFound) + self.setRowCount(upperBound) + #only visible rows will be filled with data + if upperBound > 0: + #don't trigger setItem's signals for each cell, instead emit dataChanged for all cells + self.blockSignals(True) + for x in range(0, upperBound): + q.next() + for col in range(0, len(self.headerLabels)): + self.setItem(x, col, QStandardItem(q.value(col))) + self.blockSignals(False) + self.dataChanged.emit(self.createIndex(0,0), self.createIndex(upperBound, len(self.headerLabels))) + + #form a condition string for the query: if filterStr is (partially) present in any of the columns + def getMatch (self, filterStr): + match = {} + for column in self.distinct.keys(): + match[column] = [] + for value in self.distinct[column]: + if filterStr in value: + match[column].append(value) + matchStr = None + if any([match[col] for col in match]): + matchStr = '( ' + if match['time']: + matchStr += "time IN ('" + "','".join(match['time']) + "') OR" + if match['process']: + matchStr += "process IN ('" + "','".join(match['process']) + "') OR" + if match['dst_host']: + matchStr += " (dst_host != '' AND dst_host IN ('" + "','".join(match['dst_host']) + "') ) OR" + if match['dst_ip']: + matchStr += " (dst_host = '' AND dst_ip IN ('" + "','".join(match['dst_ip']) + "') ) OR" + if match['dst_port']: + matchStr += " dst_port IN ('" + "','".join(match['dst_port']) + "') OR" + if match['rule']: + matchStr += " rule IN ('" + "','".join(match['rule']) + "') OR" + if match['node']: + matchStr += " node IN ('" + "','".join(match['node']) + "') OR" + if match['protocol']: + matchStr += " protocol IN ('" + "','".join(match['protocol']) + "') OR" + matchStr = matchStr[:-2] #remove trailing 'OR' + matchStr += ' )' + return matchStr + + #extract the filter string if any + def getFilterStr(self): + filterStr = None + if "LIKE '%" in self.origQueryStr: + filterStr = self.origQueryStr.split("LIKE '%")[1].split("%")[0] + return filterStr + + #extract the action string if any + def getActionStr(self): + actionStr = None + if 'WHERE Action = "' in self.origQueryStr: + actionCond = self.origQueryStr.split('WHERE Action = "')[1].split('"')[0] + actionStr = "action = '"+actionCond+"'" + return actionStr + + def dumpRows(self): + rows = [] + q = QSqlQuery(self.db) + q.exec(self.origQueryStr) + q.seek(QSql.BeforeFirstRow) + while True: + q.next() + if q.at() == QSql.AfterLastRow: + break + row = [] + for col in range(0, len(self.headerLabels)): + row.append(q.value(col)) + rows.append(row) + return rows + +class ConnectionsTableView(QTableView): + # how many rows can potentially be displayed in viewport + # the actual number of rows currently displayed may be less than this + maxRowsInViewport = 0 + #vertical scroll bar + vScrollBar = None + + def __init__(self, parent): + QTableView.__init__(self, parent) + #eventFilter to catch key up/down events and wheel events + self.installEventFilter(self) + self.verticalHeader().setVisible(False) + self.horizontalHeader().setDefaultAlignment(QtCore.Qt.AlignCenter) + self.horizontalHeader().setStretchLastSection(True) + #the built-in vertical scrollBar of this view is always off + self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + + def setVerticalScrollBar(self, vScrollBar): + self.vScrollBar = vScrollBar + self.vScrollBar.valueChanged.connect(self.onValueChanged) + self.vScrollBar.setVisible(False) + + def setModel(self, model): + super().setModel(model) + model.rowCountChanged.connect(self.onRowCountChanged) + model.rowsInserted.connect(self.onRowsInsertedOrRemoved) + model.rowsRemoved.connect(self.onRowsInsertedOrRemoved) + self.horizontalHeader().sortIndicatorChanged.disconnect() + self.setSortingEnabled(False) + + #model().rowCount() is always <= self.maxRowsInViewport + #stretch the bottom row; we don't want partial-height rows at the bottom + #this will only trigger if rowCount value was changed + def onRowsInsertedOrRemoved(self, parent, start, end): + if self.model().rowCount() == self.maxRowsInViewport: + self.verticalHeader().setStretchLastSection(True) + else: + self.verticalHeader().setStretchLastSection(False) + + def resizeEvent(self, event): + super().resizeEvent(event) + #refresh the viewport data based on new geometry + self.calculateRowsInViewport() + self.model().setRowCount(min(self.maxRowsInViewport, self.model().totalRowCount)) + self.model().refreshViewport(self.vScrollBar.value(), self.maxRowsInViewport) + + def calculateRowsInViewport(self): + rowHeight = self.verticalHeader().defaultSectionSize() + #we don't want partial-height rows in viewport, hence .floor() + self.maxRowsInViewport = math.floor(self.viewport().height() / rowHeight) + + def onValueChanged(self, vSBNewValue): + savedIndex = self.selectionModel().currentIndex() + self.model().refreshViewport(vSBNewValue, self.maxRowsInViewport) + #restore selection which was removed by model's refreshing the data + self.selectionModel().setCurrentIndex(savedIndex, QItemSelectionModel.Rows | QItemSelectionModel.SelectCurrent) + + # if ( scrollbar at the top or row limit set): + # let new rows "push down" older rows without changing the scrollbar position + # else: + # don't update data in viewport, only change scrollbar position. + def onRowCountChanged(self): + totalCount = self.model().totalRowCount + scrollBar = self.vScrollBar + scrollBar.setVisible(True if totalCount > self.maxRowsInViewport else False) + scrollBarValue = scrollBar.value() + if self.model().limit: + newValue = min(scrollBarValue, self.model().limit - self.maxRowsInViewport) + scrollBar.setMinimum(0) + scrollBar.setMaximum( min(totalCount, self.model().limit) - self.maxRowsInViewport) + if scrollBarValue != newValue: + #setValue does not trigger valueChanged if new value is the same as old + scrollBar.setValue(newValue) + else: + scrollBar.valueChanged.emit(newValue) + else: + scrollBar.setMinimum(0) + scrollBar.setMaximum(max(0, totalCount - self.maxRowsInViewport)) + if scrollBarValue == 0: + scrollBar.valueChanged.emit(0) + elif scrollBarValue > 0: + if self.model().prependedRowCount == 0: + scrollBar.valueChanged.emit(scrollBarValue) + else: + scrollBar.setValue(scrollBarValue + self.model().prependedRowCount) + + def onKeyUp(self): + if self.selectionModel().currentIndex().row() == 0: + self.vScrollBar.setValue(self.vScrollBar.value() - 1) + + def onKeyDown(self): + if self.selectionModel().currentIndex().row() == self.maxRowsInViewport - 1: + self.vScrollBar.setValue(self.vScrollBar.value() + 1) + + def onKeyHome(self): + self.vScrollBar.setValue(0) + self.selectionModel().setCurrentIndex(self.model().createIndex(0, 0), QItemSelectionModel.Rows | QItemSelectionModel.SelectCurrent) + + def onKeyEnd(self): + self.vScrollBar.setValue(self.vScrollBar.maximum()) + self.selectionModel().setCurrentIndex(self.model().createIndex(min(self.maxRowsInViewport, self.model().totalRowCount) - 1, 0), QItemSelectionModel.Rows | QItemSelectionModel.SelectCurrent) + + def onKeyPageUp(self): + #scroll up only when on the first row + if self.selectionModel().currentIndex().row() != 0: + return + self.vScrollBar.setValue(self.vScrollBar.value() - self.maxRowsInViewport) + + def onKeyPageDown(self): + #scroll down only when on the last row + if self.selectionModel().currentIndex().row() != self.maxRowsInViewport - 1: + return + self.vScrollBar.setValue(self.vScrollBar.value() + self.maxRowsInViewport) + + def eventFilter(self, obj, event): + if event.type() == QEvent.KeyPress: + if event.key() == QtCore.Qt.Key_Up: + self.onKeyUp() + elif event.key() == QtCore.Qt.Key_Down: + self.onKeyDown() + elif event.key() == QtCore.Qt.Key_Home: + self.onKeyHome() + elif event.key() == QtCore.Qt.Key_End: + self.onKeyEnd() + elif event.key() == QtCore.Qt.Key_PageUp: + self.onKeyPageUp() + elif event.key() == QtCore.Qt.Key_PageDown: + self.onKeyPageDown() + elif event.type() == QEvent.Wheel: + self.vScrollBar.wheelEvent(event) + return False diff --git a/ui/opensnitch/database.py b/ui/opensnitch/database.py new file mode 100644 index 0000000..ed93a11 --- /dev/null +++ b/ui/opensnitch/database.py @@ -0,0 +1,439 @@ +from PyQt5.QtSql import QSqlDatabase, QSqlQueryModel, QSqlQuery +import threading +import sys +from datetime import datetime, timedelta + +class Database: + db = None + __instance = None + DB_IN_MEMORY = ":memory:" + DB_TYPE_MEMORY = 0 + DB_TYPE_FILE = 1 + + @staticmethod + def instance(): + if Database.__instance == None: + Database.__instance = Database() + return Database.__instance + + def __init__(self, dbname="db"): + self._lock = threading.RLock() + self.db = None + self.db_file = Database.DB_IN_MEMORY + self.db_name = dbname + + def initialize(self, dbtype=DB_TYPE_MEMORY, dbfile=DB_IN_MEMORY, db_name="db"): + if dbtype != Database.DB_TYPE_MEMORY: + self.db_file = dbfile + + self.db = QSqlDatabase.addDatabase("QSQLITE", self.db_name) + self.db.setDatabaseName(self.db_file) + if not self.db.open(): + print("\n ** Error opening DB: SQLite driver not loaded. DB name: %s\n" % self.db_file) + print("\n Available drivers: ", QSqlDatabase.drivers()) + sys.exit(-1) + + db_status, db_error = self.is_db_ok() + if db_status is False: + print("db.initialize() error:", db_error) + return False, db_error + + self._create_tables() + return True, None + + def close(self): + try: + if self.db.isOpen(): + self.db.removeDatabase(self.db_name) + self.db.close() + except Exception as e: + print("db.close() exception:", e) + + def is_db_ok(self): + # XXX: quick_check may not be fast enough with some DBs on slow + # hardware. + q = QSqlQuery("PRAGMA quick_check;", self.db) + if q.exec_() is not True: + print(q.lastError().driverText()) + return False, q.lastError().driverText() + + if q.next() and q.value(0) != "ok": + return False, "Database is corrupted (1)" + + return True, None + + def get_db(self): + return self.db + + def get_db_file(self): + return self.db_file + + def get_new_qsql_model(self): + return QSqlQueryModel() + + def get_db_name(self): + return self.db_name + + def _create_tables(self): + # https://www.sqlite.org/wal.html + if self.db_file == Database.DB_IN_MEMORY: + q = QSqlQuery("PRAGMA journal_mode = OFF", self.db) + q.exec_() + q = QSqlQuery("PRAGMA synchronous = OFF", self.db) + q.exec_() + q = QSqlQuery("PRAGMA cache_size=10000", self.db) + q.exec_() + else: + q = QSqlQuery("PRAGMA synchronous = NORMAL", self.db) + q.exec_() + + q = QSqlQuery("create table if not exists connections (" \ + "time text, " \ + "node text, " \ + "action text, " \ + "protocol text, " \ + "src_ip text, " \ + "src_port text, " \ + "dst_ip text, " \ + "dst_host text, " \ + "dst_port text, " \ + "uid text, " \ + "pid text, " \ + "process text, " \ + "process_args text, " \ + "process_cwd text, " \ + "rule text, " \ + "UNIQUE(node, action, protocol, src_ip, src_port, dst_ip, dst_port, uid, pid, process, process_args))", + self.db) + q = QSqlQuery("create index time_index on connections (time)", self.db) + q.exec_() + q = QSqlQuery("create index action_index on connections (action)", self.db) + q.exec_() + q = QSqlQuery("create index protocol_index on connections (protocol)", self.db) + q.exec_() + q = QSqlQuery("create index dst_host_index on connections (dst_host)", self.db) + q.exec_() + q = QSqlQuery("create index process_index on connections (process)", self.db) + q.exec_() + q = QSqlQuery("create index dst_ip_index on connections (dst_ip)", self.db) + q.exec_() + q = QSqlQuery("create index dst_port_index on connections (dst_port)", self.db) + q.exec_() + q = QSqlQuery("create index rule_index on connections (rule)", self.db) + q.exec_() + q = QSqlQuery("create index node_index on connections (node)", self.db) + q.exec_() + q = QSqlQuery("CREATE INDEX details_query_index on connections (process, process_args, uid, pid, dst_ip, dst_host, dst_port, action, node, protocol)", self.db) + q.exec_() + q = QSqlQuery("create table if not exists rules (" \ + "time text, " \ + "node text, " \ + "name text, " \ + "enabled text, " \ + "precedence text, " \ + "action text, " \ + "duration text, " \ + "operator_type text, " \ + "operator_sensitive text, " \ + "operator_operand text, " \ + "operator_data text, " \ + "UNIQUE(node, name)" + ")", self.db) + q.exec_() + q = QSqlQuery("create index rules_index on rules (time)", self.db) + q.exec_() + + q = QSqlQuery("create table if not exists hosts (what text primary key, hits integer)", self.db) + q.exec_() + q = QSqlQuery("create table if not exists procs (what text primary key, hits integer)", self.db) + q.exec_() + q = QSqlQuery("create table if not exists addrs (what text primary key, hits integer)", self.db) + q.exec_() + q = QSqlQuery("create table if not exists ports (what text primary key, hits integer)", self.db) + q.exec_() + q = QSqlQuery("create table if not exists users (what text primary key, hits integer)", self.db) + q.exec_() + + q = QSqlQuery("create table if not exists nodes (" \ + "addr text primary key," \ + "hostname text," \ + "daemon_version text," \ + "daemon_uptime text," \ + "daemon_rules text," \ + "cons text," \ + "cons_dropped text," \ + "version text," \ + "status text, " \ + "last_connection text)" + , self.db) + q.exec_() + + def optimize(self): + """https://www.sqlite.org/pragma.html#pragma_optimize + """ + q = QSqlQuery("PRAGMA optimize;", self.db) + q.exec_() + + def clean(self, table): + with self._lock: + q = QSqlQuery("delete from " + table, self.db) + q.exec_() + + def vacuum(self): + q = QSqlQuery("VACUUM;", self.db) + q.exec_() + + def clone_db(self, name): + return QSqlDatabase.cloneDatabase(self.db, name) + + def clone(self): + q = QSqlQuery(".dump", self.db) + q.exec_() + + def transaction(self): + self.db.transaction() + + def commit(self): + self.db.commit() + + def rollback(self): + self.db.rollback() + + def get_total_records(self): + try: + q = QSqlQuery("SELECT count(*) FROM connections", self.db) + if q.exec_() and q.first(): + r = q.value(0) + except Exception as e: + print("db, get_total_records() error:", e) + + def get_newest_record(self): + try: + q = QSqlQuery("SELECT time FROM connections ORDER BY 1 DESC LIMIT 1", self.db) + if q.exec_() and q.first(): + return q.value(0) + except Exception as e: + print("db, get_newest_record() error:", e) + return 0 + + def get_oldest_record(self): + try: + q = QSqlQuery("SELECT time FROM connections ORDER BY 1 ASC LIMIT 1", self.db) + if q.exec_() and q.first(): + return q.value(0) + except Exception as e: + print("db, get_oldest_record() error:", e) + return 0 + + def purge_oldest(self, max_days_to_keep): + try: + oldt = self.get_oldest_record() + newt = self.get_newest_record() + if oldt == None or newt == None or oldt == 0 or newt == 0: + return -1 + + oldest = datetime.fromisoformat(oldt) + newest = datetime.fromisoformat(newt) + diff = newest - oldest + date_to_purge = datetime.now() - timedelta(days=max_days_to_keep) + + if diff.days >= max_days_to_keep: + q = QSqlQuery(self.db) + q.prepare("DELETE FROM connections WHERE time < ?") + q.bindValue(0, str(date_to_purge)) + if q.exec_(): + print("purge_oldest() {0} records deleted".format(q.numRowsAffected())) + return q.numRowsAffected() + except Exception as e: + print("db, purge_oldest() error:", e) + + return -1 + + def select(self, qstr): + try: + return QSqlQuery(qstr, self.db) + except Exception as e: + print("db, select() exception: ", e) + + return None + + def remove(self, qstr): + try: + q = QSqlQuery(qstr, self.db) + if q.exec_(): + return True + else: + print("db, remove() ERROR: ", qstr) + print(q.lastError().driverText()) + except Exception as e: + print("db, remove exception: ", e) + + return False + + def _insert(self, query_str, columns): + with self._lock: + try: + + q = QSqlQuery(self.db) + q.prepare(query_str) + for idx, v in enumerate(columns): + q.bindValue(idx, v) + if q.exec_(): + return True + else: + print("_insert() ERROR", query_str) + print(q.lastError().driverText()) + + except Exception as e: + print("_insert exception", e) + finally: + q.finish() + + return False + + def insert(self, table, fields, columns, update_field=None, update_values=None, action_on_conflict="REPLACE"): + if update_field != None: + action_on_conflict = "" + else: + action_on_conflict = "OR " + action_on_conflict + + qstr = "INSERT " + action_on_conflict + " INTO " + table + " " + fields + " VALUES(" + update_fields="" + for col in columns: + qstr += "?," + qstr = qstr[0:len(qstr)-1] + ")" + + if update_field != None: + # NOTE: UPSERTS on sqlite are only supported from v3.24 on. + # On Ubuntu16.04/18 for example (v3.11/3.22) updating a record on conflict + # fails with "Parameter count error" + qstr += " ON CONFLICT (" + update_field + ") DO UPDATE SET " + for idx, field in enumerate(update_values): + qstr += str(field) + "=excluded." + str(field) + "," + + qstr = qstr[0:len(qstr)-1] + + return self._insert(qstr, columns) + + def update(self, table, fields, values, condition, action_on_conflict="OR IGNORE"): + qstr = "UPDATE " + action_on_conflict + " " + table + " SET " + fields + " WHERE " + condition + try: + with self._lock: + q = QSqlQuery(qstr, self.db) + q.prepare(qstr) + for idx, v in enumerate(values): + q.bindValue(idx, v) + if not q.exec_(): + print("update ERROR", qstr) + print(q.lastError().driverText()) + + except Exception as e: + print("update() exception:", e) + finally: + q.finish() + + def _insert_batch(self, query_str, fields, values): + result=True + with self._lock: + try: + q = QSqlQuery(self.db) + q.prepare(query_str) + q.addBindValue(fields) + q.addBindValue(values) + if not q.execBatch(): + print("_insert_batch() error", query_str) + print(q.lastError().driverText()) + + result=False + except Exception as e: + print("_insert_batch() exception:", e) + finally: + q.finish() + + return result + + def insert_batch(self, table, db_fields, db_columns, fields, values, update_field=None, update_value=None, action_on_conflict="REPLACE"): + action = "OR " + action_on_conflict + if update_field != None: + action = "" + + qstr = "INSERT " + action + " INTO " + table + " (" + db_fields[0] + "," + db_fields[1] + ") VALUES(" + for idx in db_columns: + qstr += "?," + qstr = qstr[0:len(qstr)-1] + ")" + + if self._insert_batch(qstr, fields, values) == False: + self.update_batch(table, db_fields, db_columns, fields, values, update_field, update_value, action_on_conflict) + + def update_batch(self, table, db_fields, db_columns, fields, values, update_field=None, update_value=None, action_on_conflict="REPLACE"): + for idx, i in enumerate(values): + s = "UPDATE " + table + " SET " + "%s=(select hits from %s)+%s" % (db_fields[1], table, values[idx]) + s += " WHERE %s=\"%s\"," % (db_fields[0], fields[idx]) + s = s[0:len(s)-1] + with self._lock: + q = QSqlQuery(s, self.db) + if not q.exec_(): + print("update batch ERROR", s) + print(q.lastError().driverText()) + + def dump(self): + q = QSqlQuery(".dump", db=self.db) + q.exec_() + + def get_query(self, table, fields): + return "SELECT " + fields + " FROM " + table + + def empty_rule(self, name=""): + if name == "": + return + qstr = "DELETE FROM connections WHERE rule = ?" + + with self._lock: + q = QSqlQuery(qstr, self.db) + q.prepare(qstr) + q.addBindValue(name) + if not q.exec_(): + print("db, empty_rule() ERROR: ", qstr) + print(q.lastError().driverText()) + + def delete_rule(self, name, node_addr): + qstr = "DELETE FROM rules WHERE name=?" + if node_addr != None: + qstr = qstr + " AND node=?" + + with self._lock: + q = QSqlQuery(qstr, self.db) + q.prepare(qstr) + q.addBindValue(name) + if node_addr != None: + q.addBindValue(node_addr) + if not q.exec_(): + print("db, delete_rule() ERROR: ", qstr) + print(q.lastError().driverText()) + + def get_rule(self, rule_name, node_addr=None): + """ + get rule records, given the name of the rule and the node + """ + qstr = "SELECT * from rules WHERE name=?" + if node_addr != None: + qstr = qstr + " AND node=?" + + q = QSqlQuery(qstr, self.db) + q.prepare(qstr) + q.addBindValue(rule_name) + if node_addr != None: + q.addBindValue(node_addr) + q.exec_() + + return q + + def insert_rule(self, rule, node_addr): + self.insert("rules", + "(time, node, name, enabled, precedence, action, duration, operator_type, operator_sensitive, operator_operand, operator_data)", + (datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + node_addr, rule.name, + str(rule.enabled), str(rule.precedence), + rule.action, rule.duration, rule.operator.type, + str(rule.operator.sensitive), rule.operator.operand, rule.operator.data), + action_on_conflict="IGNORE") diff --git a/ui/opensnitch/desktop_parser.py b/ui/opensnitch/desktop_parser.py new file mode 100644 index 0000000..04ab962 --- /dev/null +++ b/ui/opensnitch/desktop_parser.py @@ -0,0 +1,178 @@ +from threading import Lock +import configparser +import pyinotify +import threading +import glob +import os +import re +import shutil +import locale + +DESKTOP_PATHS = tuple([ + os.path.join(d, 'applications') + for d in os.getenv('XDG_DATA_DIRS', '/usr/share/').split(':') +]) + +class LinuxDesktopParser(threading.Thread): + def __init__(self): + threading.Thread.__init__(self) + self.lock = Lock() + self.daemon = True + self.running = False + self.apps = {} + self.apps_by_name = {} + self.get_locale() + # some things are just weird + # (not really, i don't want to keep track of parent pids + # just because of icons though, this hack is way easier) + self.fixes = { + '/opt/google/chrome/chrome': '/opt/google/chrome/google-chrome', + '/usr/lib/firefox/firefox': '/usr/lib/firefox/firefox.sh', + '/usr/bin/pidgin.orig': '/usr/bin/pidgin' + } + + for desktop_path in DESKTOP_PATHS: + if not os.path.exists(desktop_path): + continue + for desktop_file in glob.glob(os.path.join(desktop_path, '*.desktop')): + self._parse_desktop_file(desktop_file) + + self.start() + + def get_locale(self): + try: + self.locale = locale.getlocale()[0] + self.locale_country = self.locale.split("_")[0] + except Exception: + self.locale = "" + self.locale_country = "" + + def _parse_exec(self, cmd): + # remove stuff like %U + cmd = re.sub( r'%[a-zA-Z]+', '', cmd) + # remove 'env .... command' + cmd = re.sub( r'^env\s+[^\s]+\s', '', cmd) + # split && trim + cmd = cmd.split(' ')[0].strip() + # remove quotes + cmd = re.sub( r'["\']+', '', cmd) + # check if we need to resolve the path + if len(cmd) > 0 and cmd[0] != '/': + for path in os.environ["PATH"].split(os.pathsep): + filename = os.path.join(path, cmd) + if os.path.exists(filename): + cmd = filename + break + + return cmd + + def get_app_description(self, parser): + try: + desc = parser.get('Desktop Entry', 'Comment[%s]' % self.locale_country, raw=True, fallback=None) + if desc == None: + desc = parser.get('Desktop Entry', 'Comment[%s]' % self.locale, raw=True, fallback=None) + + if desc == None: + desc = parser.get('Desktop Entry', 'Comment', raw=True, fallback=None) + + return desc + except: + return None + + def discover_app_icon(self, app_name): + # more hacks + # normally qt will find icons if the system if configured properly. + # if it's not, qt won't be able to find the icon by using QIcon().fromTheme(""), + # so we fallback to try to determine if the icon exist in some well known system paths. + icon_dirs = ("/usr/share/icons/gnome/48x48/apps/", "/usr/share/pixmaps/", "/usr/share/icons/hicolor/48x48/apps/") + icon_exts = (".png", ".xpm", ".svg") + for idir in icon_dirs: + for iext in icon_exts: + if iext in app_name: + iconPath = idir + app_name + if os.path.exists(iconPath): + return iconPath + else: + iconPath = idir + app_name + iext + if os.path.exists(iconPath): + return iconPath + + def _parse_desktop_file(self, desktop_path): + parser = configparser.ConfigParser(strict=False) # Allow duplicate config entries + try: + basename = os.path.basename(desktop_path)[:-8] + parser.read(desktop_path, 'utf8') + + cmd = parser.get('Desktop Entry', 'exec', raw=True, fallback=None) + if cmd == None: + cmd = parser.get('Desktop Entry', 'Exec', raw=True, fallback=None) + if cmd is not None: + cmd = self._parse_exec(cmd) + icon = parser.get('Desktop Entry', 'Icon', raw=True, fallback=None) + name = parser.get('Desktop Entry', 'Name', raw=True, fallback=None) + desc = self.get_app_description(parser) + + if icon == None: + # Some .desktop files doesn't have the Icon entry + # FIXME: even if we return an icon, if the DE is not properly configured, + # it won't be loaded/displayed. + icon = self.discover_app_icon(basename) + + with self.lock: + # The Exec entry may have an absolute path to a binary or just the binary with parameters. + # /path/binary or binary, so save both + self.apps[cmd] = (name, icon, desc, desktop_path) + self.apps[basename] = (name, icon, desc, desktop_path) + # if the command is a symlink, add the real binary too + if os.path.islink(cmd): + link_to = os.path.realpath(cmd) + self.apps[link_to] = (name, icon, desc, desktop_path) + except: + print("Exception parsing .desktop file ", desktop_path) + + def get_info_by_path(self, path, default_icon): + def_name = os.path.basename(path) + # apply fixes + for orig, to in self.fixes.items(): + if path == orig: + path = to + break + + app_name = self.apps.get(path) + if app_name == None: + # last try to get a default terminal icon + for def_icon in ("utilities-terminal", "gnome-terminal", "xfce-terminal"): + test = self.apps.get(def_name, (def_name, def_icon, "", None)) + if test != None: + return test + + return self.apps.get(def_name, (def_name, default_icon, "", None)) + + return self.apps.get(path, (def_name, default_icon, "", None)) + + def get_info_by_binname(self, name, default_icon): + def_name = os.path.basename(name) + return self.apps.get(def_name, (def_name, default_icon, None)) + + def run(self): + self.running = True + wm = pyinotify.WatchManager() + notifier = pyinotify.Notifier(wm) + + def inotify_callback(event): + if event.mask == pyinotify.IN_CLOSE_WRITE: + self._parse_desktop_file(event.pathname) + + elif event.mask == pyinotify.IN_DELETE: + with self.lock: + for cmd, data in self.apps.items(): + if data[2] == event.pathname: + del self.apps[cmd] + break + + for p in DESKTOP_PATHS: + if os.path.exists(p): + wm.add_watch(p, + pyinotify.IN_CLOSE_WRITE | pyinotify.IN_DELETE, + inotify_callback) + notifier.loop() diff --git a/ui/opensnitch/dialogs/__init__.py b/ui/opensnitch/dialogs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ui/opensnitch/dialogs/preferences.py b/ui/opensnitch/dialogs/preferences.py new file mode 100644 index 0000000..e102429 --- /dev/null +++ b/ui/opensnitch/dialogs/preferences.py @@ -0,0 +1,553 @@ +import sys +import time +import os +import json + +from PyQt5 import QtCore, QtGui, uic, QtWidgets +from PyQt5.QtCore import QCoreApplication as QC + +from opensnitch.config import Config +from opensnitch.nodes import Nodes +from opensnitch.database import Database +from opensnitch.utils import Message, QuickHelp, Themes +from opensnitch.notifications import DesktopNotifications + +from opensnitch import ui_pb2 + +DIALOG_UI_PATH = "%s/../res/preferences.ui" % os.path.dirname(sys.modules[__name__].__file__) +class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): + + LOG_TAG = "[Preferences] " + _notification_callback = QtCore.pyqtSignal(ui_pb2.NotificationReply) + saved = QtCore.pyqtSignal() + + TAB_POPUPS = 0 + TAB_UI = 1 + TAB_NODES = 2 + TAB_DB = 3 + + SUM = 1 + REST = 0 + + def __init__(self, parent=None, appicon=None): + QtWidgets.QDialog.__init__(self, parent, QtCore.Qt.WindowStaysOnTopHint) + + self._themes = Themes.instance() + self._saved_theme = "" + + self._cfg = Config.get() + self._nodes = Nodes.instance() + self._db = Database.instance() + + self._notification_callback.connect(self._cb_notification_callback) + self._notifications_sent = {} + self._desktop_notifications = DesktopNotifications() + + self.setupUi(self) + self.setWindowIcon(appicon) + + self.dbFileButton.setVisible(False) + self.dbLabel.setVisible(False) + self.dbType = None + + self.acceptButton.clicked.connect(self._cb_accept_button_clicked) + self.applyButton.clicked.connect(self._cb_apply_button_clicked) + self.cancelButton.clicked.connect(self._cb_cancel_button_clicked) + self.helpButton.clicked.connect(self._cb_help_button_clicked) + self.popupsCheck.clicked.connect(self._cb_popups_check_toggled) + self.dbFileButton.clicked.connect(self._cb_file_db_clicked) + self.checkUIRules.toggled.connect(self._cb_check_ui_rules_toggled) + self.cmdTimeoutUp.clicked.connect(lambda: self._cb_cmd_spin_clicked(self.spinUITimeout, self.SUM)) + self.cmdTimeoutDown.clicked.connect(lambda: self._cb_cmd_spin_clicked(self.spinUITimeout, self.REST)) + self.cmdDBMaxDaysUp.clicked.connect(lambda: self._cb_cmd_spin_clicked(self.spinDBMaxDays, self.SUM)) + self.cmdDBMaxDaysDown.clicked.connect(lambda: self._cb_cmd_spin_clicked(self.spinDBMaxDays, self.REST)) + self.cmdDBPurgesUp.clicked.connect(lambda: self._cb_cmd_spin_clicked(self.spinDBPurgeInterval, self.SUM)) + self.cmdDBPurgesDown.clicked.connect(lambda: self._cb_cmd_spin_clicked(self.spinDBPurgeInterval, self.REST)) + self.cmdTestNotifs.clicked.connect(self._cb_test_notifs_clicked) + self.radioSysNotifs.clicked.connect(self._cb_radio_system_notifications) + self.helpButton.setToolTipDuration(30 * 1000) + + if QtGui.QIcon.hasThemeIcon("emblem-default") == False: + self.applyButton.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_DialogApplyButton"))) + self.cancelButton.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_DialogCloseButton"))) + self.acceptButton.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_DialogSaveButton"))) + self.dbFileButton.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_DirOpenIcon"))) + + if QtGui.QIcon.hasThemeIcon("list-add") == False: + self.cmdTimeoutUp.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_ArrowUp"))) + self.cmdTimeoutDown.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_ArrowDown"))) + self.cmdDBMaxDaysUp.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_ArrowUp"))) + self.cmdDBMaxDaysDown.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_ArrowDown"))) + self.cmdDBPurgesUp.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_ArrowUp"))) + self.cmdDBPurgesDown.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_ArrowDown"))) + + def showEvent(self, event): + super(PreferencesDialog, self).showEvent(event) + + try: + self._settingsSaved = False + self._reset_status_message() + self._hide_status_label() + self.comboNodes.clear() + + self._node_list = self._nodes.get() + for addr in self._node_list: + self.comboNodes.addItem(addr) + + if len(self._node_list) == 0: + self._reset_node_settings() + except Exception as e: + print(self.LOG_TAG + "exception loading nodes", e) + + self._load_settings() + + # connect the signals after loading settings, to avoid firing + # the signals + self.comboNodes.currentIndexChanged.connect(self._cb_node_combo_changed) + self.comboNodeAction.currentIndexChanged.connect(self._cb_node_needs_update) + self.comboNodeDuration.currentIndexChanged.connect(self._cb_node_needs_update) + self.comboNodeMonitorMethod.currentIndexChanged.connect(self._cb_node_needs_update) + self.comboNodeLogLevel.currentIndexChanged.connect(self._cb_node_needs_update) + self.comboNodeLogFile.currentIndexChanged.connect(self._cb_node_needs_update) + self.comboNodeAddress.currentTextChanged.connect(self._cb_node_needs_update) + self.checkInterceptUnknown.clicked.connect(self._cb_node_needs_update) + self.checkApplyToNodes.clicked.connect(self._cb_node_needs_update) + self.comboDBType.currentIndexChanged.connect(self._cb_db_type_changed) + self.checkDBMaxDays.toggled.connect(self._cb_db_max_days_toggled) + + # True when any node option changes + self._node_needs_update = False + + def _load_themes(self): + theme_idx, self._saved_theme = self._themes.get_saved_theme() + + self.labelThemeError.setVisible(False) + self.labelThemeError.setText("") + self.comboUITheme.clear() + self.comboUITheme.addItem(QC.translate("preferences", "System")) + if self._themes.available(): + themes = self._themes.list_themes() + self.comboUITheme.addItems(themes) + else: + self._saved_theme = "" + self.labelThemeError.setStyleSheet('color: red') + self.labelThemeError.setVisible(True) + self.labelThemeError.setText(QC.translate("preferences", "Themes not available. Install qt-material: pip3 install qt-material")) + + self.comboUITheme.setCurrentIndex(theme_idx) + + def _load_settings(self): + self._default_action = self._cfg.getInt(self._cfg.DEFAULT_ACTION_KEY) + self._default_target = self._cfg.getInt(self._cfg.DEFAULT_TARGET_KEY, 0) + self._default_timeout = self._cfg.getInt(self._cfg.DEFAULT_TIMEOUT_KEY, Config.DEFAULT_TIMEOUT) + self._disable_popups = self._cfg.getBool(self._cfg.DEFAULT_DISABLE_POPUPS) + + if self._cfg.hasKey(self._cfg.DEFAULT_DURATION_KEY): + self._default_duration = self._cfg.getInt(self._cfg.DEFAULT_DURATION_KEY) + else: + self._default_duration = self._cfg.DEFAULT_DURATION_IDX + + self.comboUIDuration.setCurrentIndex(self._default_duration) + self.comboUIDialogPos.setCurrentIndex(self._cfg.getInt(self._cfg.DEFAULT_POPUP_POSITION)) + + self.comboUIRules.setCurrentIndex(self._cfg.getInt(self._cfg.DEFAULT_IGNORE_TEMPORARY_RULES)) + self.checkUIRules.setChecked(self._cfg.getBool(self._cfg.DEFAULT_IGNORE_RULES)) + self.comboUIRules.setEnabled(self._cfg.getBool(self._cfg.DEFAULT_IGNORE_RULES)) + #self._set_rules_duration_filter() + + self._cfg.setRulesDurationFilter( + self._cfg.getBool(self._cfg.DEFAULT_IGNORE_RULES), + self._cfg.getInt(self._cfg.DEFAULT_IGNORE_TEMPORARY_RULES) + ) + + self.comboUIAction.setCurrentIndex(self._default_action) + self.comboUITarget.setCurrentIndex(self._default_target) + self.spinUITimeout.setValue(self._default_timeout) + self.spinUITimeout.setEnabled(not self._disable_popups) + self.popupsCheck.setChecked(self._disable_popups) + + self.showAdvancedCheck.setChecked(self._cfg.getBool(self._cfg.DEFAULT_POPUP_ADVANCED)) + self.dstIPCheck.setChecked(self._cfg.getBool(self._cfg.DEFAULT_POPUP_ADVANCED_DSTIP)) + self.dstPortCheck.setChecked(self._cfg.getBool(self._cfg.DEFAULT_POPUP_ADVANCED_DSTPORT)) + self.uidCheck.setChecked(self._cfg.getBool(self._cfg.DEFAULT_POPUP_ADVANCED_UID)) + + # by default, if no configuration exists, enable notifications. + self.groupNotifs.setChecked(self._cfg.getBool(Config.NOTIFICATIONS_ENABLED, True)) + self.radioSysNotifs.setChecked( + True if self._cfg.getInt(Config.NOTIFICATIONS_TYPE) == Config.NOTIFICATION_TYPE_SYSTEM and self._desktop_notifications.is_available() == True else False + ) + self.radioQtNotifs.setChecked( + True if self._cfg.getInt(Config.NOTIFICATIONS_TYPE) == Config.NOTIFICATION_TYPE_QT or self._desktop_notifications.is_available() == False else False + ) + + self.dbType = self._cfg.getInt(self._cfg.DEFAULT_DB_TYPE_KEY) + self.comboDBType.setCurrentIndex(self.dbType) + if self.comboDBType.currentIndex() != Database.DB_TYPE_MEMORY: + self.dbFileButton.setVisible(True) + self.dbLabel.setVisible(True) + self.dbLabel.setText(self._cfg.getSettings(self._cfg.DEFAULT_DB_FILE_KEY)) + dbMaxDays = self._cfg.getInt(self._cfg.DEFAULT_DB_MAX_DAYS, 1) + dbPurgeInterval = self._cfg.getInt(self._cfg.DEFAULT_DB_PURGE_INTERVAL, 5) + self._enable_db_cleaner_options(self._cfg.getBool(Config.DEFAULT_DB_PURGE_OLDEST), dbMaxDays) + self.spinDBMaxDays.setValue(dbMaxDays) + self.spinDBPurgeInterval.setValue(dbPurgeInterval) + + self._load_themes() + self._load_node_settings() + self._load_ui_columns_config() + + def _load_node_settings(self): + addr = self.comboNodes.currentText() + if addr != "": + try: + node_data = self._node_list[addr]['data'] + self.labelNodeVersion.setText(node_data.version) + self.labelNodeName.setText(node_data.name) + self.comboNodeLogLevel.setCurrentIndex(node_data.logLevel) + + node_config = json.loads(node_data.config) + self.comboNodeAction.setCurrentText(node_config['DefaultAction']) + self.comboNodeDuration.setCurrentText(node_config['DefaultDuration']) + self.comboNodeMonitorMethod.setCurrentText(node_config['ProcMonitorMethod']) + self.checkInterceptUnknown.setChecked(node_config['InterceptUnknown']) + self.comboNodeLogLevel.setCurrentIndex(int(node_config['LogLevel'])) + + if node_config.get('Server') != None: + self.comboNodeAddress.setEnabled(True) + self.comboNodeLogFile.setEnabled(True) + + self.comboNodeAddress.setCurrentText(node_config['Server']['Address']) + self.comboNodeLogFile.setCurrentText(node_config['Server']['LogFile']) + else: + self.comboNodeAddress.setEnabled(False) + self.comboNodeLogFile.setEnabled(False) + except Exception as e: + print(self.LOG_TAG + "exception loading config: ", e) + + def _load_node_config(self, addr): + try: + if self.comboNodeAddress.currentText() == "": + return None, QC.translate("preferences", "Server address can not be empty") + + node_action = Config.ACTION_DENY + if self.comboNodeAction.currentIndex() == 1: + node_action = Config.ACTION_ALLOW + + node_duration = Config.DURATION_ONCE + if self.comboNodeDuration.currentIndex() == 1: + node_duration = Config.DURATION_UNTIL_RESTART + elif self.comboNodeDuration.currentIndex() == 2: + node_duration = Config.DURATION_ALWAYS + + node_config = json.loads(self._nodes.get_node_config(addr)) + node_config['DefaultAction'] = node_action + node_config['DefaultDuration'] = node_duration + node_config['ProcMonitorMethod'] = self.comboNodeMonitorMethod.currentText() + node_config['LogLevel'] = self.comboNodeLogLevel.currentIndex() + node_config['InterceptUnknown'] = self.checkInterceptUnknown.isChecked() + + if node_config.get('Server') != None: + # skip setting Server Address if we're applying the config to all nodes + if self.checkApplyToNodes.isChecked(): + node_config['Server']['Address'] = self.comboNodeAddress.currentText() + node_config['Server']['LogFile'] = self.comboNodeLogFile.currentText() + else: + print(addr, " doesn't have Server item") + return json.dumps(node_config, indent=" "), None + except Exception as e: + print(self.LOG_TAG + "exception loading node config on %s: " % addr, e) + + return None, QC.translate("preferences", "Error loading {0} configuration").format(addr) + + def _load_ui_columns_config(self): + cols = self._cfg.getSettings(Config.STATS_SHOW_COLUMNS) + if cols == None: + return + + for c in range(7): + checked = str(c) in cols + if c == 0: + self.checkHideTime.setChecked(checked) + elif c == 1: + self.checkHideNode.setChecked(checked) + elif c == 2: + self.checkHideAction.setChecked(checked) + elif c == 3: + self.checkHideDst.setChecked(checked) + elif c == 4: + self.checkHideProto.setChecked(checked) + elif c == 5: + self.checkHideProc.setChecked(checked) + elif c == 6: + self.checkHideRule.setChecked(checked) + + def _reset_node_settings(self): + self.comboNodeAction.setCurrentIndex(0) + self.comboNodeDuration.setCurrentIndex(0) + self.comboNodeMonitorMethod.setCurrentIndex(0) + self.checkInterceptUnknown.setChecked(False) + self.comboNodeLogLevel.setCurrentIndex(0) + self.labelNodeName.setText("") + self.labelNodeVersion.setText("") + + def _save_settings(self): + self._save_ui_config() + self._save_db_config() + + if self.tabWidget.currentIndex() == self.TAB_NODES: + self._show_status_label() + + addr = self.comboNodes.currentText() + if (self._node_needs_update or self.checkApplyToNodes.isChecked()) and addr != "": + try: + notif = ui_pb2.Notification( + id=int(str(time.time()).replace(".", "")), + type=ui_pb2.CHANGE_CONFIG, + data="", + rules=[]) + if self.checkApplyToNodes.isChecked(): + for addr in self._nodes.get_nodes(): + error = self._save_node_config(notif, addr) + if error != None: + self._set_status_error(error) + return + else: + error = self._save_node_config(notif, addr) + if error != None: + self._set_status_error(error) + return + except Exception as e: + print(self.LOG_TAG + "exception saving config: ", e) + self._set_status_error(QC.translate("preferences", "Exception saving config: {0}").format(str(e))) + + self._node_needs_update = False + + self.saved.emit() + self._settingsSaved = True + + def _save_db_config(self): + dbtype = self.comboDBType.currentIndex() + self._cfg.setSettings(Config.DEFAULT_DB_TYPE_KEY, dbtype) + self._cfg.setSettings(Config.DEFAULT_DB_PURGE_OLDEST, bool(self.checkDBMaxDays.isChecked())) + self._cfg.setSettings(Config.DEFAULT_DB_MAX_DAYS, int(self.spinDBMaxDays.value())) + self._cfg.setSettings(Config.DEFAULT_DB_PURGE_INTERVAL, int(self.spinDBPurgeInterval.value())) + + if self.comboDBType.currentIndex() == self.dbType: + return + + if dbtype == self._db.get_db_file(): + return + + if self.comboDBType.currentIndex() != Database.DB_TYPE_MEMORY: + if self.dbLabel.text() != "": + self._cfg.setSettings(Config.DEFAULT_DB_FILE_KEY, self.dbLabel.text()) + else: + Message.ok( + QC.translate("preferences", "Warning"), + QC.translate("preferences", "You must select a file for the database<br>or choose \"In memory\" type."), + QtWidgets.QMessageBox.Warning) + return + + Message.ok( + QC.translate("preferences", "DB type changed"), + QC.translate("preferences", "Restart the GUI in order effects to take effect"), + QtWidgets.QMessageBox.Warning) + + self.dbType = self.comboDBType.currentIndex() + + def _save_ui_config(self): + self._save_ui_columns_config() + + self._cfg.setSettings(self._cfg.DEFAULT_IGNORE_TEMPORARY_RULES, int(self.comboUIRules.currentIndex())) + self._cfg.setSettings(self._cfg.DEFAULT_IGNORE_RULES, bool(self.checkUIRules.isChecked())) + #self._set_rules_duration_filter() + self._cfg.setRulesDurationFilter( + bool(self.checkUIRules.isChecked()), + int(self.comboUIRules.currentIndex()) + ) + + self._cfg.setSettings(self._cfg.DEFAULT_ACTION_KEY, self.comboUIAction.currentIndex()) + self._cfg.setSettings(self._cfg.DEFAULT_DURATION_KEY, int(self.comboUIDuration.currentIndex())) + self._cfg.setSettings(self._cfg.DEFAULT_TARGET_KEY, self.comboUITarget.currentIndex()) + self._cfg.setSettings(self._cfg.DEFAULT_TIMEOUT_KEY, self.spinUITimeout.value()) + self._cfg.setSettings(self._cfg.DEFAULT_DISABLE_POPUPS, bool(self.popupsCheck.isChecked())) + self._cfg.setSettings(self._cfg.DEFAULT_POPUP_POSITION, int(self.comboUIDialogPos.currentIndex())) + + self._cfg.setSettings(self._cfg.DEFAULT_POPUP_ADVANCED, bool(self.showAdvancedCheck.isChecked())) + self._cfg.setSettings(self._cfg.DEFAULT_POPUP_ADVANCED_DSTIP, bool(self.dstIPCheck.isChecked())) + self._cfg.setSettings(self._cfg.DEFAULT_POPUP_ADVANCED_DSTPORT, bool(self.dstPortCheck.isChecked())) + self._cfg.setSettings(self._cfg.DEFAULT_POPUP_ADVANCED_UID, bool(self.uidCheck.isChecked())) + + self._cfg.setSettings(self._cfg.NOTIFICATIONS_ENABLED, bool(self.groupNotifs.isChecked())) + self._cfg.setSettings(self._cfg.NOTIFICATIONS_TYPE, + int(Config.NOTIFICATION_TYPE_SYSTEM if self.radioSysNotifs.isChecked() else Config.NOTIFICATION_TYPE_QT)) + + self._themes.save_theme(self.comboUITheme.currentIndex(), self.comboUITheme.currentText()) + if self._themes.available() and self._saved_theme != self.comboUITheme.currentText(): + Message.ok( + QC.translate("preferences", "UI theme changed"), + QC.translate("preferences", "Restart the GUI in order to apply the new theme"), + QtWidgets.QMessageBox.Warning) + + # this is a workaround for not display pop-ups. + # see #79 for more information. + if self.popupsCheck.isChecked(): + self._cfg.setSettings(self._cfg.DEFAULT_TIMEOUT_KEY, 0) + + def _save_ui_columns_config(self): + cols=list() + if self.checkHideTime.isChecked(): + cols.append("0") + if self.checkHideNode.isChecked(): + cols.append("1") + if self.checkHideAction.isChecked(): + cols.append("2") + if self.checkHideDst.isChecked(): + cols.append("3") + if self.checkHideProto.isChecked(): + cols.append("4") + if self.checkHideProc.isChecked(): + cols.append("5") + if self.checkHideRule.isChecked(): + cols.append("6") + + self._cfg.setSettings(Config.STATS_SHOW_COLUMNS, cols) + + def _save_node_config(self, notifObject, addr): + try: + self._set_status_message(QC.translate("preferences", "Applying configuration on {0} ...").format(addr)) + notifObject.data, error = self._load_node_config(addr) + if error != None: + return error + + if addr.startswith("unix://"): + self._cfg.setSettings(self._cfg.DEFAULT_DEFAULT_SERVER_ADDR, self.comboNodeAddress.currentText()) + else: + self._nodes.save_node_config(addr, notifObject.data) + nid = self._nodes.send_notification(addr, notifObject, self._notification_callback) + + self._notifications_sent[nid] = notifObject + except Exception as e: + print(self.LOG_TAG + "exception saving node config on %s: " % addr, e) + self._set_status_error(QC.translate("Exception saving node config {0}: {1}").format((addr, str(e)))) + return addr + ": " + str(e) + + return None + + def _hide_status_label(self): + self.statusLabel.hide() + + def _show_status_label(self): + self.statusLabel.show() + + def _set_status_error(self, msg): + self._show_status_label() + self.statusLabel.setStyleSheet('color: red') + self.statusLabel.setText(msg) + + def _set_status_successful(self, msg): + self._show_status_label() + self.statusLabel.setStyleSheet('color: green') + self.statusLabel.setText(msg) + + def _set_status_message(self, msg): + self._show_status_label() + self.statusLabel.setStyleSheet('color: darkorange') + self.statusLabel.setText(msg) + + def _reset_status_message(self): + self.statusLabel.setText("") + self._hide_status_label() + + def _enable_db_cleaner_options(self, enable, db_max_days): + self.checkDBMaxDays.setChecked(enable) + self.spinDBMaxDays.setEnabled(enable) + self.spinDBPurgeInterval.setEnabled(enable) + self.labelDBPurgeInterval.setEnabled(enable) + self.cmdDBMaxDaysUp.setEnabled(enable) + self.cmdDBMaxDaysDown.setEnabled(enable) + self.cmdDBPurgesUp.setEnabled(enable) + self.cmdDBPurgesDown.setEnabled(enable) + + @QtCore.pyqtSlot(ui_pb2.NotificationReply) + def _cb_notification_callback(self, reply): + #print(self.LOG_TAG, "Config notification received: ", reply.id, reply.code) + if reply.id in self._notifications_sent: + if reply.code == ui_pb2.OK: + self._set_status_successful(QC.translate("preferences", "Configuration applied.")) + else: + self._set_status_error(QC.translate("preferences", "Error applying configuration: {0}").format(reply.data)) + + del self._notifications_sent[reply.id] + + def _cb_file_db_clicked(self): + options = QtWidgets.QFileDialog.Options() + fileName, _ = QtWidgets.QFileDialog.getSaveFileName(self, "", "","All Files (*)", options=options) + if fileName: + self.dbLabel.setText(fileName) + + def _cb_db_type_changed(self): + if self.comboDBType.currentIndex() == Database.DB_TYPE_MEMORY: + self.dbFileButton.setVisible(False) + self.dbLabel.setVisible(False) + else: + self.dbFileButton.setVisible(True) + self.dbLabel.setVisible(True) + + def _cb_accept_button_clicked(self): + self.accept() + if not self._settingsSaved: + self._save_settings() + + def _cb_apply_button_clicked(self): + self._save_settings() + + def _cb_cancel_button_clicked(self): + self.reject() + + def _cb_help_button_clicked(self): + QuickHelp.show( + QC.translate("preferences", + "Hover the mouse over the texts to display the help<br><br>Don't forget to visit the wiki: <a href=\"{0}\">{0}</a>" + ).format(Config.HELP_URL) + ) + + def _cb_popups_check_toggled(self, checked): + self.spinUITimeout.setEnabled(not checked) + if not checked: + self.spinUITimeout.setValue(15) + + def _cb_node_combo_changed(self, index): + self._load_node_settings() + + def _cb_node_needs_update(self): + self._node_needs_update = True + + def _cb_check_ui_rules_toggled(self, state): + self.comboUIRules.setEnabled(state) + + def _cb_db_max_days_toggled(self, state): + self._enable_db_cleaner_options(state, 1) + + def _cb_cmd_spin_clicked(self, spinWidget, operation): + if operation == self.SUM: + spinWidget.setValue(spinWidget.value() + 1) + else: + spinWidget.setValue(spinWidget.value() - 1) + + def _cb_radio_system_notifications(self): + if self._desktop_notifications.is_available() == False: + self.radioSysNotifs.setChecked(False) + self.radioQtNotifs.setChecked(True) + self._set_status_error(QC.translate("notifications", "System notifications are not available, you need to install python3-notify2.")) + return + + def _cb_test_notifs_clicked(self): + if self._desktop_notifications.is_available() == False: + self._set_status_error(QC.translate("notifications", "System notifications are not available, you need to install python3-notify2.")) + return + + if self.radioSysNotifs.isChecked(): + self._desktop_notifications.show("title", "body") + else: + pass diff --git a/ui/opensnitch/dialogs/processdetails.py b/ui/opensnitch/dialogs/processdetails.py new file mode 100644 index 0000000..67cc630 --- /dev/null +++ b/ui/opensnitch/dialogs/processdetails.py @@ -0,0 +1,329 @@ +import os +import sys +import json + +from PyQt5 import QtCore, QtGui, uic, QtWidgets + +from opensnitch import ui_pb2 +from opensnitch.nodes import Nodes +from opensnitch.desktop_parser import LinuxDesktopParser +from opensnitch.utils import Message + +DIALOG_UI_PATH = "%s/../res/process_details.ui" % os.path.dirname(sys.modules[__name__].__file__) +class ProcessDetailsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): + + LOG_TAG = "[ProcessDetails]: " + + _notification_callback = QtCore.pyqtSignal(ui_pb2.NotificationReply) + + TAB_STATUS = 0 + TAB_DESCRIPTORS = 1 + TAB_IOSTATS = 2 + TAB_MAPS = 3 + TAB_STACK = 4 + TAB_ENVS = 5 + + TABS = { + TAB_STATUS: { + "text": None, + "scrollPos": 0 + }, + TAB_DESCRIPTORS: { + "text": None, + "scrollPos": 0 + }, + TAB_IOSTATS: { + "text": None, + "scrollPos": 0 + }, + TAB_MAPS: { + "text": None, + "scrollPos": 0 + }, + TAB_STACK: { + "text": None, + "scrollPos": 0 + }, + TAB_ENVS: { + "text": None, + "scrollPos": 0 + } + } + + def __init__(self, parent=None, appicon=None): + super(ProcessDetailsDialog, self).__init__(parent) + QtWidgets.QDialog.__init__(self, parent, QtCore.Qt.WindowStaysOnTopHint) + self.setWindowFlags(QtCore.Qt.Window) + self.setupUi(self) + self.setWindowIcon(appicon) + + self._app_name = None + self._app_icon = None + self._apps_parser = LinuxDesktopParser() + self._nodes = Nodes.instance() + self._notification_callback.connect(self._cb_notification_callback) + + self._nid = None + self._pid = "" + self._notifications_sent = {} + + self.cmdClose.clicked.connect(self._cb_close_clicked) + self.cmdAction.clicked.connect(self._cb_action_clicked) + self.comboPids.currentIndexChanged.connect(self._cb_combo_pids_changed) + + self.TABS[self.TAB_STATUS]['text'] = self.textStatus + self.TABS[self.TAB_DESCRIPTORS]['text'] = self.textOpenedFiles + self.TABS[self.TAB_IOSTATS]['text'] = self.textIOStats + self.TABS[self.TAB_MAPS]['text'] = self.textMappedFiles + self.TABS[self.TAB_STACK]['text'] = self.textStack + self.TABS[self.TAB_ENVS]['text'] = self.textEnv + + self.TABS[self.TAB_DESCRIPTORS]['text'].setFont(QtGui.QFont("monospace")) + + self.iconStart = QtGui.QIcon.fromTheme("media-playback-start") + self.iconPause = QtGui.QIcon.fromTheme("media-playback-pause") + + if QtGui.QIcon.hasThemeIcon("window-close") == False: + self.cmdClose.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_DialogCloseButton"))) + self.iconStart = self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_MediaPlay")) + self.iconPause = self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_MediaPause")) + + @QtCore.pyqtSlot(ui_pb2.NotificationReply) + def _cb_notification_callback(self, reply): + if reply.id in self._notifications_sent: + noti = self._notifications_sent[reply.id] + + if reply.code == ui_pb2.ERROR: + self._show_message(QtCore.QCoreApplication.translate("proc_details", "<b>Error loading process information:</b> <br><br>\n\n") + reply.data) + self._pid = "" + self._set_button_running(False) + + # if we haven't loaded any data yet, just close the window + if self._data_loaded == False: + # but if there're more than 1 pid keep the window open. + # we may have one pid already closed and one alive. + if self.comboPids.count() <= 1: + self._close() + + self._delete_notification(reply.id) + return + + if noti.type == ui_pb2.MONITOR_PROCESS and reply.data != "": + self._load_data(reply.data) + + elif noti.type == ui_pb2.STOP_MONITOR_PROCESS: + if reply.data != "": + self._show_message(QtCore.QCoreApplication.translate("proc_details", "<b>Error stopping monitoring process:</b><br><br>") + reply.data) + self._set_button_running(False) + + self._delete_notification(reply.id) + else: + print("[stats] unknown notification received: ", reply.id) + + def closeEvent(self, e): + self._close() + + def _cb_close_clicked(self): + self._close() + + def _cb_combo_pids_changed(self, idx): + if idx == -1: + return + # TODO: this event causes to send to 2 Start notifications + #if self._pid != "" and self._pid != self.comboPids.currentText(): + # self._stop_monitoring() + # self._pid = self.comboPids.currentText() + # self._start_monitoring() + + def _cb_action_clicked(self): + if not self.cmdAction.isChecked(): + self._stop_monitoring() + else: + self._start_monitoring() + + def _show_message(self, text): + Message.ok(text, "", QtWidgets.QMessageBox.Warning) + + def _delete_notification(self, nid): + if nid in self._notifications_sent: + del self._notifications_sent[nid] + + def _reset(self): + self._app_name = None + self._app_icon = None + self.comboPids.clear() + self.labelProcName.setText(QtCore.QCoreApplication.translate("proc_details", "loading...")) + self.labelProcArgs.setText(QtCore.QCoreApplication.translate("proc_details", "loading...")) + self.labelProcIcon.clear() + self.labelStatm.setText("") + self.labelCwd.setText("") + for tidx in range(0, len(self.TABS)): + self.TABS[tidx]['text'].setPlainText("") + + def _set_button_running(self, yes): + if yes: + self.cmdAction.setChecked(True) + self.cmdAction.setIcon(self.iconPause) + else: + self.cmdAction.setChecked(False) + self.cmdAction.setIcon(self.iconStart) + + def _close(self): + self._stop_monitoring() + self.comboPids.clear() + self._pid = "" + self.hide() + + def monitor(self, pids): + if self._pid != "": + self._stop_monitoring() + + self._data_loaded = False + self._pids = pids + self._reset() + for pid in pids: + if pid != None: + self.comboPids.addItem(pid) + + self.show() + self._start_monitoring() + + def _set_tab_text(self, tab_idx, text): + self.TABS[tab_idx]['scrollPos'] = self.TABS[tab_idx]['text'].verticalScrollBar().value() + self.TABS[tab_idx]['text'].setPlainText(text) + self.TABS[tab_idx]['text'].verticalScrollBar().setValue(self.TABS[tab_idx]['scrollPos']) + + def _start_monitoring(self): + try: + # avoid to send notifications without a pid + if self._pid != "": + return + + self._pid = self.comboPids.currentText() + if self._pid == "": + return + + self._set_button_running(True) + noti = ui_pb2.Notification(clientName="", serverName="", type=ui_pb2.MONITOR_PROCESS, data=self._pid, rules=[]) + self._nid = self._nodes.send_notification(self._pids[self._pid], noti, self._notification_callback) + self._notifications_sent[self._nid] = noti + except Exception as e: + print(self.LOG_TAG + "exception starting monitoring: ", e) + + def _stop_monitoring(self): + if self._pid == "": + return + + self._set_button_running(False) + noti = ui_pb2.Notification(clientName="", serverName="", type=ui_pb2.STOP_MONITOR_PROCESS, data=str(self._pid), rules=[]) + self._nid = self._nodes.send_notification(self._pids[self._pid], noti, self._notification_callback) + self._notifications_sent[self._nid] = noti + self._pid = "" + self._app_icon = None + + def _load_data(self, data): + tab_idx = self.tabWidget.currentIndex() + + try: + proc = json.loads(data) + self._load_app_icon(proc['Path']) + if self._app_name != None: + self.labelProcName.setText("<b>" + self._app_name + "</b>") + self.labelProcName.setToolTip("<b>" + self._app_name + "</b>") + + #if proc['Path'] not in proc['Args']: + # proc['Args'].insert(0, proc['Path']) + + self.labelProcArgs.setFixedHeight(30) + self.labelProcArgs.setText(" ".join(proc['Args'])) + self.labelProcArgs.setToolTip(" ".join(proc['Args'])) + self.labelCwd.setText("<b>CWD: </b>" + proc['CWD']) + self.labelCwd.setToolTip("<b>CWD: </b>" + proc['CWD']) + self._load_mem_data(proc['Statm']) + + if tab_idx == self.TAB_STATUS: + self._set_tab_text(tab_idx, proc['Status']) + + elif tab_idx == self.TAB_DESCRIPTORS: + self._load_descriptors(proc['Descriptors']) + + elif tab_idx == self.TAB_IOSTATS: + self._load_iostats(proc['IOStats']) + + elif tab_idx == self.TAB_MAPS: + self._set_tab_text(tab_idx, proc['Maps']) + + elif tab_idx == self.TAB_STACK: + self._set_tab_text(tab_idx, proc['Stack']) + + elif tab_idx == self.TAB_ENVS: + self._load_env_vars(proc['Env']) + + self._data_loaded = True + + except Exception as e: + print(self.LOG_TAG + "exception loading data: ", e) + + def _load_app_icon(self, proc_path): + if self._app_icon != None: + return + + self._app_name, self._app_icon, _, _ = self._apps_parser.get_info_by_path(proc_path, "terminal") + + icon = QtGui.QIcon().fromTheme(self._app_icon) + pixmap = icon.pixmap(icon.actualSize(QtCore.QSize(48, 48))) + self.labelProcIcon.setPixmap(pixmap) + + if self._app_name == None: + self._app_name = proc_path + + def _load_iostats(self, iostats): + ioText = "%-16s %dMB<br>%-16s %dMB<br>%-16s %d<br>%-16s %d<br>%-16s %dMB<br>%-16s %dMB<br>" % ( + "<b>Chars read:</b>", + ((iostats['RChar'] / 1024) / 1024), + "<b>Chars written:</b>", + ((iostats['WChar'] / 1024) / 1024), + "<b>Syscalls read:</b>", + (iostats['SyscallRead']), + "<b>Syscalls write:</b>", + (iostats['SyscallWrite']), + "<b>KB read:</b>", + ((iostats['ReadBytes'] / 1024) / 1024), + "<b>KB written: </b>", + ((iostats['WriteBytes'] / 1024) / 1024) + ) + + self.textIOStats.setPlainText("") + self.textIOStats.appendHtml(ioText) + + def _load_mem_data(self, mem): + # assuming page size == 4096 + pagesize = 4096 + memText = "<b>VIRT:</b> %dMB, <b>RSS:</b> %dMB, <b>Libs:</b> %dMB, <b>Data:</b> %dMB, <b>Text:</b> %dMB" % ( + ((mem['Size'] * pagesize) / 1024) / 1024, + ((mem['Resident'] * pagesize) / 1024) / 1024, + ((mem['Lib'] * pagesize) / 1024) / 1024, + ((mem['Data'] * pagesize) / 1024) / 1024, + ((mem['Text'] * pagesize) / 1024) / 1024 + ) + self.labelStatm.setText(memText) + + def _load_descriptors(self, descriptors): + text = "%-12s%-40s%-8s -> %s\n\n" % ("Size", "Time", "Name", "Symlink") + for d in descriptors: + text += "{:<12}{:<40}{:<8} -> {}\n".format(str(d['Size']), d['ModTime'], d['Name'], d['SymLink']) + + self._set_tab_text(self.TAB_DESCRIPTORS, text) + + def _load_env_vars(self, envs): + if envs == {}: + self._set_tab_text(self.TAB_ENVS, "<no environment variables>") + return + + text = "%-15s\t%s\n\n" % ("Name", "Value") + for env_name in envs: + text += "%-15s:\t%s\n" % (env_name, envs[env_name]) + + self._set_tab_text(self.TAB_ENVS, text) + + diff --git a/ui/opensnitch/dialogs/prompt.py b/ui/opensnitch/dialogs/prompt.py new file mode 100644 index 0000000..5cd0173 --- /dev/null +++ b/ui/opensnitch/dialogs/prompt.py @@ -0,0 +1,588 @@ +import threading +import sys +import time +import os +import os.path +import pwd +import json +import ipaddress + +from PyQt5 import QtCore, QtGui, uic, QtWidgets +from PyQt5.QtCore import QCoreApplication as QC + +from slugify import slugify + +from opensnitch.desktop_parser import LinuxDesktopParser +from opensnitch.config import Config +from opensnitch.version import version + +from opensnitch import ui_pb2 + +DIALOG_UI_PATH = "%s/../res/prompt.ui" % os.path.dirname(sys.modules[__name__].__file__) +class PromptDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): + _prompt_trigger = QtCore.pyqtSignal() + _tick_trigger = QtCore.pyqtSignal() + _timeout_trigger = QtCore.pyqtSignal() + + DEFAULT_TIMEOUT = 15 + + ACTION_IDX_DENY = 0 + ACTION_IDX_ALLOW = 1 + + FIELD_REGEX_HOST = "regex_host" + FIELD_REGEX_IP = "regex_ip" + FIELD_PROC_PATH = "process_path" + FIELD_PROC_ARGS = "process_args" + FIELD_PROC_ID = "process_id" + FIELD_USER_ID = "user_id" + FIELD_DST_IP = "dst_ip" + FIELD_DST_PORT = "dst_port" + FIELD_DST_NETWORK = "dst_network" + FIELD_DST_HOST = "simple_host" + + # don't translate + DURATION_30s = "30s" + DURATION_5m = "5m" + DURATION_15m = "15m" + DURATION_30m = "30m" + DURATION_1h = "1h" + # don't translate + + # label displayed in the pop-up combo + DURATION_session = QC.translate("popups", "until reboot") + # label displayed in the pop-up combo + DURATION_forever = QC.translate("popups", "forever") + + def __init__(self, parent=None, appicon=None): + QtWidgets.QDialog.__init__(self, parent, QtCore.Qt.WindowStaysOnTopHint) + # Other interesting flags: QtCore.Qt.Tool | QtCore.Qt.BypassWindowManagerHint + self._cfg = Config.get() + self.setupUi(self) + self.setWindowIcon(appicon) + + self._width = None + self._height = None + + dialog_geometry = self._cfg.getSettings("promptDialog/geometry") + if dialog_geometry == QtCore.QByteArray: + self.restoreGeometry(dialog_geometry) + + self.setWindowTitle("OpenSnitch v%s" % version) + + self._lock = threading.Lock() + self._con = None + self._rule = None + self._local = True + self._peer = None + self._prompt_trigger.connect(self.on_connection_prompt_triggered) + self._timeout_trigger.connect(self.on_timeout_triggered) + self._tick_trigger.connect(self.on_tick_triggered) + self._tick = int(self._cfg.getSettings(self._cfg.DEFAULT_TIMEOUT_KEY)) if self._cfg.hasKey(self._cfg.DEFAULT_TIMEOUT_KEY) else self.DEFAULT_TIMEOUT + self._tick_thread = None + self._done = threading.Event() + self._timeout_text = "" + self._timeout_triggered = False + + self._apps_parser = LinuxDesktopParser() + + self.denyButton.clicked.connect(self._on_deny_clicked) + # also accept button + self.applyButton.clicked.connect(self._on_apply_clicked) + self._apply_text = QC.translate("popups", "Allow") + self._deny_text = QC.translate("popups", "Deny") + self._default_action = self._cfg.getInt(self._cfg.DEFAULT_ACTION_KEY) + + self.whatIPCombo.setVisible(False) + self.checkDstIP.setVisible(False) + self.checkDstPort.setVisible(False) + self.checkUserID.setVisible(False) + self.appDescriptionLabel.setVisible(False) + + self._ischeckAdvanceded = False + self.checkAdvanced.toggled.connect(self._check_advanced_toggled) + + self.checkAdvanced.clicked.connect(self._button_clicked) + self.durationCombo.activated.connect(self._button_clicked) + self.whatCombo.activated.connect(self._button_clicked) + self.whatIPCombo.activated.connect(self._button_clicked) + self.checkDstIP.clicked.connect(self._button_clicked) + self.checkDstPort.clicked.connect(self._button_clicked) + self.checkUserID.clicked.connect(self._button_clicked) + + if QtGui.QIcon.hasThemeIcon("emblem-default") == False: + self.applyButton.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_DialogApplyButton"))) + self.denyButton.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_DialogCancelButton"))) + + def showEvent(self, event): + super(PromptDialog, self).showEvent(event) + self.activateWindow() + + if self._width is None or self._height is None: + self._width = self.width() + self._height = self.height() + + self.setMinimumSize(self._width, self._height) + self.setMaximumSize(self._width, self._height) + self.move_popup() + + def move_popup(self): + popup_pos = self._cfg.getInt(self._cfg.DEFAULT_POPUP_POSITION) + point = QtWidgets.QDesktopWidget().availableGeometry() + if popup_pos == self._cfg.POPUP_TOP_RIGHT: + self.move(point.topRight()) + elif popup_pos == self._cfg.POPUP_TOP_LEFT: + self.move(point.topLeft()) + elif popup_pos == self._cfg.POPUP_BOTTOM_RIGHT: + self.move(point.bottomRight()) + elif popup_pos == self._cfg.POPUP_BOTTOM_LEFT: + self.move(point.bottomLeft()) + + def _stop_countdown(self): + self.applyButton.setText("%s" % self._apply_text) + self.denyButton.setText("%s" % self._deny_text) + self._tick_thread.stop = True + + def _check_advanced_toggled(self, state): + self.checkDstIP.setVisible(state) + self.whatIPCombo.setVisible(state) + self.destIPLabel.setVisible(not state) + self.checkDstPort.setVisible(state) + self.checkUserID.setVisible(state) + self._ischeckAdvanceded = state + + def _button_clicked(self): + self._stop_countdown() + + def _set_elide_text(self, widget, text, max_size=132): + if len(text) > max_size: + text = text[:max_size] + "..." + + widget.setText(text) + + def promptUser(self, connection, is_local, peer): + # one at a time + with self._lock: + # reset state + if self._tick_thread != None and self._tick_thread.is_alive(): + self._tick_thread.join() + self._cfg.reload() + self._tick = int(self._cfg.getSettings(self._cfg.DEFAULT_TIMEOUT_KEY)) if self._cfg.hasKey(self._cfg.DEFAULT_TIMEOUT_KEY) else self.DEFAULT_TIMEOUT + self._tick_thread = threading.Thread(target=self._timeout_worker) + self._tick_thread.stop = self._ischeckAdvanceded + self._timeout_triggered = False + self._rule = None + self._local = is_local + self._peer = peer + self._con = connection + self._done.clear() + # trigger and show dialog + self._prompt_trigger.emit() + # start timeout thread + self._tick_thread.start() + # wait for user choice or timeout + self._done.wait() + + return self._rule, self._timeout_triggered + + def _timeout_worker(self): + if self._tick == 0: + self._timeout_trigger.emit() + return + + while self._tick > 0 and self._done.is_set() is False: + t = threading.currentThread() + # stop only stops the coundtdown, not the thread itself. + if getattr(t, "stop", True): + self._tick = int(self._cfg.getSettings(self._cfg.DEFAULT_TIMEOUT_KEY)) + time.sleep(1) + continue + + self._tick -= 1 + self._tick_trigger.emit() + time.sleep(1) + + if not self._done.is_set(): + self._timeout_trigger.emit() + + @QtCore.pyqtSlot() + def on_connection_prompt_triggered(self): + self._render_connection(self._con) + if self._tick > 0: + self.show() + + @QtCore.pyqtSlot() + def on_tick_triggered(self): + self._set_cmd_action_text() + + @QtCore.pyqtSlot() + def on_timeout_triggered(self): + self._timeout_triggered = True + self._send_rule() + + def _configure_default_duration(self): + if self._cfg.hasKey(self._cfg.DEFAULT_DURATION_KEY): + cur_idx = self._cfg.getInt(self._cfg.DEFAULT_DURATION_KEY) + self.durationCombo.setCurrentIndex(cur_idx) + else: + self.durationCombo.setCurrentIndex(self._cfg.DEFAULT_DURATION_IDX) + + def _set_cmd_action_text(self): + if self._cfg.getInt(self._cfg.DEFAULT_ACTION_KEY) == self.ACTION_IDX_ALLOW: + self.applyButton.setText("%s (%d)" % (self._apply_text, self._tick)) + self.denyButton.setText(self._deny_text) + else: + self.denyButton.setText("%s (%d)" % (self._deny_text, self._tick)) + self.applyButton.setText(self._apply_text) + + def _set_app_description(self, description): + if description != None and description != "": + self.appDescriptionLabel.setVisible(True) + self.appDescriptionLabel.setFixedHeight(50) + self.appDescriptionLabel.setToolTip(description) + self._set_elide_text(self.appDescriptionLabel, "%s" % description) + else: + self.appDescriptionLabel.setVisible(False) + self.appDescriptionLabel.setFixedHeight(0) + self.appDescriptionLabel.setText("") + + def _set_app_path(self, app_name, app_args, con): + # show the binary path if it's not part of the cmdline args: + # cmdline: telnet 1.1.1.1 (path: /usr/bin/telnet.netkit) + # cmdline: /usr/bin/telnet.netkit 1.1.1.1 (the binary path is part of the cmdline args, no need to display it) + if con.process_path != "" and len(con.process_args) >= 1 and con.process_path not in con.process_args: + self.appPathLabel.setToolTip("Process path: %s" % con.process_path) + if app_name.lower() == app_args: + self._set_elide_text(self.appPathLabel, "%s" % con.process_path) + else: + self._set_elide_text(self.appPathLabel, "(%s)" % con.process_path) + self.appPathLabel.setVisible(True) + else: + self.appPathLabel.setVisible(False) + self.appPathLabel.setText("") + + def _set_app_args(self, app_name, app_args): + # if the app name and the args are the same, there's no need to display + # the args label (amule for example) + if app_name.lower() != app_args: + self.argsLabel.setVisible(True) + self._set_elide_text(self.argsLabel, app_args) + self.argsLabel.setToolTip(app_args) + else: + self.argsLabel.setVisible(False) + self.argsLabel.setText("") + + def _render_connection(self, con): + app_name, app_icon, description, _ = self._apps_parser.get_info_by_path(con.process_path, "terminal") + app_args = " ".join(con.process_args) + self._set_app_description(description) + self._set_app_path(app_name, app_args, con) + self._set_app_args(app_name, app_args) + + if app_name == "": + self.appPathLabel.setVisible(False) + self.argsLabel.setVisible(False) + app_name = QC.translate("popups", "Unknown process %s" % con.process_path) + self.appNameLabel.setText(QC.translate("popups", "Outgoing connection")) + else: + self._set_elide_text(self.appNameLabel, "%s" % app_name, max_size=42) + self.appNameLabel.setToolTip(app_name) + + self.cwdLabel.setToolTip("%s %s" % (QC.translate("popups", "Process launched from:"), con.process_cwd)) + self._set_elide_text(self.cwdLabel, con.process_cwd, max_size=32) + + pixmap = self._get_app_icon(app_icon) + self.iconLabel.setPixmap(pixmap) + + message = self._get_popup_message(app_name, con) + + self.messageLabel.setText(message) + self.messageLabel.setToolTip(message) + + self.sourceIPLabel.setText(con.src_ip) + self.destIPLabel.setText(con.dst_ip) + self.destPortLabel.setText(str(con.dst_port)) + + if self._local: + try: + uid = "%d (%s)" % (con.user_id, pwd.getpwuid(con.user_id).pw_name) + except: + uid = "" + else: + uid = "%d" % con.user_id + + self.uidLabel.setText(uid) + self.pidLabel.setText("%s" % con.process_id) + + self.whatCombo.clear() + self.whatIPCombo.clear() + + # the order of these combobox entries must match those in the preferences dialog + # prefs -> UI -> Default target + self.whatCombo.addItem(QC.translate("popups", "from this executable"), self.FIELD_PROC_PATH) + if int(con.process_id) < 0: + self.whatCombo.model().item(0).setEnabled(False) + + self.whatCombo.addItem(QC.translate("popups", "from this command line"), self.FIELD_PROC_ARGS) + + self.whatCombo.addItem(QC.translate("popups", "to port {0}").format(con.dst_port), self.FIELD_DST_PORT) + self.whatCombo.addItem(QC.translate("popups", "to {0}").format(con.dst_ip), self.FIELD_DST_IP) + + self.whatCombo.addItem(QC.translate("popups", "from user {0}").format(uid), self.FIELD_USER_ID) + if int(con.user_id) < 0: + self.whatCombo.model().item(4).setEnabled(False) + + self.whatCombo.addItem(QC.translate("popups", "from this PID"), self.FIELD_PROC_ID) + + self._add_dst_networks_to_combo(self.whatCombo, con.dst_ip) + + if con.dst_host != "" and con.dst_host != con.dst_ip: + self._add_dsthost_to_combo(con.dst_host) + + self.whatIPCombo.addItem(QC.translate("popups", "to {0}").format(con.dst_ip), self.FIELD_DST_IP) + + parts = con.dst_ip.split('.') + nparts = len(parts) + for i in range(1, nparts): + self.whatCombo.addItem(QC.translate("popups", "to {0}.*").format('.'.join(parts[:i])), self.FIELD_REGEX_IP) + self.whatIPCombo.addItem(QC.translate("popups", "to {0}.*").format( '.'.join(parts[:i])), self.FIELD_REGEX_IP) + + self._add_dst_networks_to_combo(self.whatIPCombo, con.dst_ip) + + self._default_action = self._cfg.getInt(self._cfg.DEFAULT_ACTION_KEY) + + self._configure_default_duration() + + if int(con.process_id) > 0: + self.whatCombo.setCurrentIndex(int(self._cfg.getSettings(self._cfg.DEFAULT_TARGET_KEY))) + else: + self.whatCombo.setCurrentIndex(2) + + + self.checkDstIP.setChecked(self._cfg.getBool(self._cfg.DEFAULT_POPUP_ADVANCED_DSTIP)) + self.checkDstPort.setChecked(self._cfg.getBool(self._cfg.DEFAULT_POPUP_ADVANCED_DSTPORT)) + self.checkUserID.setChecked(self._cfg.getBool(self._cfg.DEFAULT_POPUP_ADVANCED_UID)) + if self._cfg.getBool(self._cfg.DEFAULT_POPUP_ADVANCED): + self.checkAdvanced.toggle() + + self._set_cmd_action_text() + self.checkAdvanced.setFocus() + + self.setFixedSize(self.size()) + + # https://gis.stackexchange.com/questions/86398/how-to-disable-the-escape-key-for-a-dialog + def keyPressEvent(self, event): + if not event.key() == QtCore.Qt.Key_Escape: + super(PromptDialog, self).keyPressEvent(event) + + # prevent a click on the window's x + # from quitting the whole application + def closeEvent(self, e): + self._send_rule() + e.ignore() + + def _add_dst_networks_to_combo(self, combo, dst_ip): + if type(ipaddress.ip_address(dst_ip)) == ipaddress.IPv4Address: + combo.addItem(QC.translate("popups", "to {0}").format(ipaddress.ip_network(dst_ip + "/24", strict=False)), self.FIELD_DST_NETWORK) + combo.addItem(QC.translate("popups", "to {0}").format(ipaddress.ip_network(dst_ip + "/16", strict=False)), self.FIELD_DST_NETWORK) + combo.addItem(QC.translate("popups", "to {0}").format(ipaddress.ip_network(dst_ip + "/8", strict=False)), self.FIELD_DST_NETWORK) + else: + combo.addItem(QC.translate("popups", "to {0}").format(ipaddress.ip_network(dst_ip + "/64", strict=False)), self.FIELD_DST_NETWORK) + combo.addItem(QC.translate("popups", "to {0}").format(ipaddress.ip_network(dst_ip + "/128", strict=False)), self.FIELD_DST_NETWORK) + + def _add_dsthost_to_combo(self, dst_host): + self.whatCombo.addItem("%s" % dst_host, self.FIELD_DST_HOST) + self.whatIPCombo.addItem("%s" % dst_host, self.FIELD_DST_HOST) + + parts = dst_host.split('.')[1:] + nparts = len(parts) + for i in range(0, nparts - 1): + self.whatCombo.addItem(QC.translate("popups", "to *.{0}").format('.'.join(parts[i:])), self.FIELD_REGEX_HOST) + self.whatIPCombo.addItem(QC.translate("popups", "to *.{0}").format('.'.join(parts[i:])), self.FIELD_REGEX_HOST) + + + def _get_app_icon(self, app_icon): + """we try to get the icon of an app from the system. + If it's not found, then we'll try to search for it in common directories + of the system. + """ + try: + icon = QtGui.QIcon().fromTheme(app_icon) + pixmap = icon.pixmap(icon.actualSize(QtCore.QSize(48, 48))) + if QtGui.QIcon().hasThemeIcon(app_icon) == False or pixmap.height() == 0: + # sometimes the icon is an absolute path, sometimes it's not + if os.path.isabs(app_icon): + icon = QtGui.QIcon(app_icon) + pixmap = icon.pixmap(icon.actualSize(QtCore.QSize(48, 48))) + else: + icon_path = self._apps_parser.discover_app_icon(app_icon) + if icon_path != None: + icon = QtGui.QIcon(icon_path) + pixmap = icon.pixmap(icon.actualSize(QtCore.QSize(48, 48))) + except Exception as e: + print("Exception _get_app_icon():", e) + + return pixmap + + def _get_popup_message(self, app_name, con): + """ + _get_popup_message helps constructing the message that is displayed on + the pop-up dialog. Example: + curl is connecting to www.opensnitch.io on TCP port 443 + """ + message = "<b>%s</b>" % app_name + if not self._local: + message = QC.translate("popups", "<b>Remote</b> process %s running on <b>%s</b>") % ( \ + message, + self._peer.split(':')[1]) + + msg_action = QC.translate("popups", "is connecting to <b>%s</b> on %s port %d") % ( \ + con.dst_host or con.dst_ip, + con.protocol.upper(), + con.dst_port ) + + if con.dst_port == 53 and con.dst_ip != con.dst_host and con.dst_host != "": + msg_action = QC.translate("popups", "is attempting to resolve <b>%s</b> via %s, %s port %d") % ( \ + con.dst_host, + con.dst_ip, + con.protocol.upper(), + con.dst_port) + + return "%s %s" % (message, msg_action) + + def _get_duration(self, duration_idx): + if duration_idx == 0: + return Config.DURATION_ONCE + elif duration_idx == 1: + return self.DURATION_30s + elif duration_idx == 2: + return self.DURATION_5m + elif duration_idx == 3: + return self.DURATION_15m + elif duration_idx == 4: + return self.DURATION_30m + elif duration_idx == 5: + return self.DURATION_1h + elif duration_idx == 6: + return Config.DURATION_UNTIL_RESTART + else: + return Config.DURATION_ALWAYS + + def _get_combo_operator(self, combo, what_idx): + if combo.itemData(what_idx) == self.FIELD_PROC_PATH: + return Config.RULE_TYPE_SIMPLE, "process.path", self._con.process_path + + elif combo.itemData(what_idx) == self.FIELD_PROC_ARGS: + # this should not happen + if len(self._con.process_args) == 0 or self._con.process_args[0] == "": + return Config.RULE_TYPE_SIMPLE, "process.path", self._con.process_path + return Config.RULE_TYPE_SIMPLE, "process.command", ' '.join(self._con.process_args) + + elif combo.itemData(what_idx) == self.FIELD_PROC_ID: + return Config.RULE_TYPE_SIMPLE, "process.id", "{0}".format(self._con.process_id) + + elif combo.itemData(what_idx) == self.FIELD_USER_ID: + return Config.RULE_TYPE_SIMPLE, "user.id", "%s" % self._con.user_id + + elif combo.itemData(what_idx) == self.FIELD_DST_PORT: + return Config.RULE_TYPE_SIMPLE, "dest.port", "%s" % self._con.dst_port + + elif combo.itemData(what_idx) == self.FIELD_DST_IP: + return Config.RULE_TYPE_SIMPLE, "dest.ip", self._con.dst_ip + + elif combo.itemData(what_idx) == self.FIELD_DST_HOST: + return Config.RULE_TYPE_SIMPLE, "dest.host", combo.currentText() + + elif combo.itemData(what_idx) == self.FIELD_DST_NETWORK: + # strip "to ": "to x.x.x/20" -> "x.x.x/20" + # we assume that to is one word in all languages + parts = combo.currentText().split(' ') + text = parts[len(parts)-1] + return Config.RULE_TYPE_NETWORK, "dest.network", text + + elif combo.itemData(what_idx) == self.FIELD_REGEX_HOST: + parts = combo.currentText().split(' ') + text = parts[len(parts)-1] + # ^(|.*\.)yahoo\.com + dsthost = '\.'.join(text.split('.')).replace("*", "") + dsthost = "^(|.*\.)%s" % dsthost[2:] + return Config.RULE_TYPE_REGEXP, "dest.host", dsthost + + elif combo.itemData(what_idx) == self.FIELD_REGEX_IP: + parts = combo.currentText().split(' ') + text = parts[len(parts)-1] + return Config.RULE_TYPE_REGEXP, "dest.ip", "%s" % '\.'.join(text.split('.')).replace("*", ".*") + + def _on_deny_clicked(self): + self._default_action = self.ACTION_IDX_DENY + self._send_rule() + + def _on_apply_clicked(self): + self._default_action = self.ACTION_IDX_ALLOW + self._send_rule() + + def _is_list_rule(self): + return self.checkUserID.isChecked() or self.checkDstPort.isChecked() or self.checkDstIP.isChecked() + + def _get_rule_name(self, rule): + rule_temp_name = slugify("%s %s" % (rule.action, rule.duration)) + if self._is_list_rule(): + rule_temp_name = "%s-list" % rule_temp_name + else: + rule_temp_name = "%s-simple" % rule_temp_name + rule_temp_name = slugify("%s %s" % (rule_temp_name, rule.operator.data)) + + return rule_temp_name[:128] + + def _send_rule(self): + try: + self._cfg.setSettings("promptDialog/geometry", self.saveGeometry()) + self._rule = ui_pb2.Rule(name="user.choice") + self._rule.enabled = True + self._rule.action = Config.ACTION_DENY if self._default_action == self.ACTION_IDX_DENY else Config.ACTION_ALLOW + self._rule.duration = self._get_duration(self.durationCombo.currentIndex()) + + what_idx = self.whatCombo.currentIndex() + self._rule.operator.type, self._rule.operator.operand, self._rule.operator.data = self._get_combo_operator(self.whatCombo, what_idx) + if self._rule.operator.data == "": + print("Invalid rule, discarding: ", self._rule) + self._rule = None + return + + rule_temp_name = self._get_rule_name(self._rule) + self._rule.name = rule_temp_name + + # TODO: move to a method + data=[] + if self.checkDstIP.isChecked() and self.whatCombo.itemData(what_idx) != self.FIELD_DST_IP: + _type, _operand, _data = self._get_combo_operator(self.whatIPCombo, self.whatIPCombo.currentIndex()) + data.append({"type": _type, "operand": _operand, "data": _data}) + rule_temp_name = slugify("%s %s" % (rule_temp_name, _data)) + + if self.checkDstPort.isChecked() and self.whatCombo.itemData(what_idx) != self.FIELD_DST_PORT: + data.append({"type": Config.RULE_TYPE_SIMPLE, "operand": "dest.port", "data": str(self._con.dst_port)}) + rule_temp_name = slugify("%s %s" % (rule_temp_name, str(self._con.dst_port))) + + if self.checkUserID.isChecked() and self.whatCombo.itemData(what_idx) != self.FIELD_USER_ID: + data.append({"type": Config.RULE_TYPE_SIMPLE, "operand": "user.id", "data": str(self._con.user_id)}) + rule_temp_name = slugify("%s %s" % (rule_temp_name, str(self._con.user_id))) + + if self._is_list_rule(): + data.append({"type": self._rule.operator.type, "operand": self._rule.operator.operand, "data": self._rule.operator.data}) + self._rule.operator.data = json.dumps(data) + self._rule.operator.type = Config.RULE_TYPE_LIST + self._rule.operator.operand = Config.RULE_TYPE_LIST + + self._rule.name = rule_temp_name + + self.hide() + if self._ischeckAdvanceded: + self.checkAdvanced.toggle() + self._ischeckAdvanceded = False + + except Exception as e: + print("[pop-up] exception creating a rule:", e) + finally: + # signal that the user took a decision and + # a new rule is available + self._done.set() + self.hide() diff --git a/ui/opensnitch/dialogs/ruleseditor.py b/ui/opensnitch/dialogs/ruleseditor.py new file mode 100644 index 0000000..3aff7c9 --- /dev/null +++ b/ui/opensnitch/dialogs/ruleseditor.py @@ -0,0 +1,810 @@ + +from PyQt5 import QtCore, QtGui, uic, QtWidgets +from PyQt5.QtCore import QCoreApplication as QC +from slugify import slugify +from datetime import datetime +import re +import json +import sys +import os +from opensnitch import ui_pb2 +import time +import ipaddress + +from opensnitch.config import Config +from opensnitch.nodes import Nodes +from opensnitch.database import Database +from opensnitch.version import version +from opensnitch.utils import Message, FileDialog + +DIALOG_UI_PATH = "%s/../res/ruleseditor.ui" % os.path.dirname(sys.modules[__name__].__file__) +class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): + + LOG_TAG = "[rules editor]" + classA_net = "10\.\d{1,3}\.\d{1,3}\.\d{1,3}" + classB_net = "172\.1[6-9]\.\d+\.\d+|172\.2[0-9]\.\d+\.\d+|172\.3[0-1]+\.\d{1,3}\.\d{1,3}" + classC_net = "192\.168\.\d{1,3}\.\d{1,3}" + others_net = "127\.\d{1,3}\.\d{1,3}\.\d{1,3}|169\.254\.\d{1,3}\.\d{1,3}" + LAN_RANGES = "^(" + others_net + "|" + classC_net + "|" + classB_net + "|" + classA_net + "|::1|f[cde].*::.*)$" + LAN_LABEL = "LAN" + + ADD_RULE = 0 + EDIT_RULE = 1 + WORK_MODE = ADD_RULE + + _notification_callback = QtCore.pyqtSignal(ui_pb2.NotificationReply) + + def __init__(self, parent=None, _rule=None, appicon=None): + super(RulesEditorDialog, self).__init__(parent) + QtWidgets.QDialog.__init__(self, parent, QtCore.Qt.WindowStaysOnTopHint) + + self._notifications_sent = {} + self._nodes = Nodes.instance() + self._db = Database.instance() + self._notification_callback.connect(self._cb_notification_callback) + self._old_rule_name = None + + self.setupUi(self) + self.setWindowIcon(appicon) + + self.buttonBox.button(QtWidgets.QDialogButtonBox.Reset).clicked.connect(self._cb_reset_clicked) + self.buttonBox.button(QtWidgets.QDialogButtonBox.Close).clicked.connect(self._cb_close_clicked) + self.buttonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self._cb_apply_clicked) + self.buttonBox.button(QtWidgets.QDialogButtonBox.Help).clicked.connect(self._cb_help_clicked) + self.selectListButton.clicked.connect(self._cb_select_list_button_clicked) + self.selectListRegexpButton.clicked.connect(self._cb_select_regexp_list_button_clicked) + self.selectIPsListButton.clicked.connect(self._cb_select_ips_list_button_clicked) + self.selectNetsListButton.clicked.connect(self._cb_select_nets_list_button_clicked) + self.protoCheck.toggled.connect(self._cb_proto_check_toggled) + self.procCheck.toggled.connect(self._cb_proc_check_toggled) + self.cmdlineCheck.toggled.connect(self._cb_cmdline_check_toggled) + self.dstPortCheck.toggled.connect(self._cb_dstport_check_toggled) + self.uidCheck.toggled.connect(self._cb_uid_check_toggled) + self.pidCheck.toggled.connect(self._cb_pid_check_toggled) + self.dstIPCheck.toggled.connect(self._cb_dstip_check_toggled) + self.dstHostCheck.toggled.connect(self._cb_dsthost_check_toggled) + self.dstListsCheck.toggled.connect(self._cb_dstlists_check_toggled) + self.dstListRegexpCheck.toggled.connect(self._cb_dstregexplists_check_toggled) + self.dstListIPsCheck.toggled.connect(self._cb_dstiplists_check_toggled) + self.dstListNetsCheck.toggled.connect(self._cb_dstnetlists_check_toggled) + + if QtGui.QIcon.hasThemeIcon("emblem-default") == False: + self.actionAllowRadio.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_DialogApplyButton"))) + self.actionDenyRadio.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_DialogCancelButton"))) + + if _rule != None: + self._load_rule(rule=_rule) + + def _bool(self, s): + return s == 'True' + + def _cb_accept_clicked(self): + pass + + def _cb_close_clicked(self): + self.hide() + + def _cb_reset_clicked(self): + self._reset_state() + + def _cb_help_clicked(self): + QtGui.QDesktopServices.openUrl(QtCore.QUrl(Config.HELP_URL)) + + def _cb_select_list_button_clicked(self): + dirName = FileDialog.select_dir(self, self.dstListsLine.text()) + if dirName != None and dirName != "": + self.dstListsLine.setText(dirName) + + def _cb_select_nets_list_button_clicked(self): + dirName = FileDialog.select_dir(self, self.dstListNetsLine.text()) + if dirName != None and dirName != "": + self.dstListNetsLine.setText(dirName) + + def _cb_select_ips_list_button_clicked(self): + dirName = FileDialog.select_dir(self, self.dstListIPsLine.text()) + if dirName != None and dirName != "": + self.dstListIPsLine.setText(dirName) + + def _cb_select_regexp_list_button_clicked(self): + dirName = FileDialog.select_dir(self, self.dstRegexpListsLine.text()) + if dirName != None and dirName != "": + self.dstRegexpListsLine.setText(dirName) + + def _cb_proto_check_toggled(self, state): + self.protoCombo.setEnabled(state) + + def _cb_proc_check_toggled(self, state): + self.procLine.setEnabled(state) + self.checkProcRegexp.setEnabled(state) + + def _cb_cmdline_check_toggled(self, state): + self.cmdlineLine.setEnabled(state) + self.checkCmdlineRegexp.setEnabled(state) + + def _cb_dstport_check_toggled(self, state): + self.dstPortLine.setEnabled(state) + + def _cb_uid_check_toggled(self, state): + self.uidLine.setEnabled(state) + + def _cb_pid_check_toggled(self, state): + self.pidLine.setEnabled(state) + + def _cb_dstip_check_toggled(self, state): + self.dstIPCombo.setEnabled(state) + + def _cb_dsthost_check_toggled(self, state): + self.dstHostLine.setEnabled(state) + + def _cb_dstlists_check_toggled(self, state): + self.dstListsLine.setEnabled(state) + self.selectListButton.setEnabled(state) + + def _cb_dstregexplists_check_toggled(self, state): + self.dstRegexpListsLine.setEnabled(state) + self.selectListRegexpButton.setEnabled(state) + + def _cb_dstiplists_check_toggled(self, state): + self.dstListIPsLine.setEnabled(state) + self.selectIPsListButton.setEnabled(state) + + def _cb_dstnetlists_check_toggled(self, state): + self.dstListNetsLine.setEnabled(state) + self.selectNetsListButton.setEnabled(state) + + def _set_status_error(self, msg): + self.statusLabel.setStyleSheet('color: red') + self.statusLabel.setText(msg) + + def _set_status_message(self, msg): + self.statusLabel.setStyleSheet('color: green') + self.statusLabel.setText(msg) + + def _cb_apply_clicked(self): + if self.nodesCombo.count() == 0: + self._set_status_error(QC.translate("rules", "There're no nodes connected.")) + return + + rule_name = self.ruleNameEdit.text() + if rule_name == "": + return + + node = self.nodesCombo.currentText() + # avoid to overwrite rules when: + # - adding a new rule. + # - when a rule is renamed, i.e., the rule is edited or added and the + # user changes the name. + if self.WORK_MODE == self.ADD_RULE and self._db.get_rule(rule_name, node).next() == True: + self._set_status_error(QC.translate("rules", "There's already a rule with this name.")) + return + elif self.WORK_MODE == self.EDIT_RULE and rule_name != self._old_rule_name and \ + self._db.get_rule(rule_name, node).next() == True: + self._set_status_error(QC.translate("rules", "There's already a rule with this name.")) + return + + result, error = self._save_rule() + if result == False: + self._set_status_error(error) + return + + self._add_rule() + if self._old_rule_name != None and self._old_rule_name != self.rule.name: + self._delete_rule() + + self._old_rule_name = rule_name + + # after adding a new rule, we enter into EDIT mode, to allow further + # changes without closing the dialog. + if self.WORK_MODE == self.ADD_RULE: + self.WORK_MODE = self.EDIT_RULE + + @QtCore.pyqtSlot(ui_pb2.NotificationReply) + def _cb_notification_callback(self, reply): + #print(self.LOG_TAG, "Rule notification received: ", reply.id, reply.code) + if reply.id in self._notifications_sent: + if reply.code == ui_pb2.OK: + self._set_status_message(QC.translate("rules", "Rule applied.")) + else: + self._set_status_error(QC.translate("rules", "Error applying rule: {0}").format(reply.data)) + + del self._notifications_sent[reply.id] + + def _get_duration(self, duration_idx): + if duration_idx == 0: + return Config.DURATION_ONCE + elif duration_idx == 1: + return Config.DURATION_30s + elif duration_idx == 2: + return Config.DURATION_5m + elif duration_idx == 3: + return Config.DURATION_15m + elif duration_idx == 4: + return Config.DURATION_30m + elif duration_idx == 5: + return Config.DURATION_1h + elif duration_idx == 6: + return Config.DURATION_UNTIL_RESTART + else: + return Config.DURATION_ALWAYS + + def _load_duration(self, duration): + if duration == Config.DURATION_ONCE: + return 0 + elif duration == Config.DURATION_30s: + return 1 + elif duration == Config.DURATION_5m: + return 2 + elif duration == Config.DURATION_15m: + return 3 + elif duration == Config.DURATION_30m: + return 4 + elif duration == Config.DURATION_1h: + return 5 + elif duration == Config.DURATION_UNTIL_RESTART: + return 6 + else: + # always + return 7 + + def _is_regex(self, text): + charset="\\*{[|^?$" + for c in charset: + if c in text: + return True + return False + + def _is_valid_regex(self, regex): + try: + re.compile(regex) + return True + except re.error as e: + self.statusLabel.setText(str(e)) + return False + + def get_rule_from_records(self, records): + rule = ui_pb2.Rule(name=records.value(2)) + rule.enabled = self._bool(records.value(3)) + rule.precedence = self._bool(records.value(4)) + rule.action = records.value(5) + rule.duration = records.value(6) + rule.operator.type = records.value(7) + rule.operator.sensitive = self._bool(records.value(8)) + rule.operator.operand = records.value(9) + rule.operator.data = "" if records.value(10) == None else str(records.value(10)) + + return rule + + def _reset_state(self): + self._old_rule_name = None + self.rule = None + + self.ruleNameEdit.setText("") + self.statusLabel.setText("") + + self.actionDenyRadio.setChecked(True) + self.durationCombo.setCurrentIndex(0) + + self.protoCheck.setChecked(False) + self.protoCombo.setCurrentText("") + + self.procCheck.setChecked(False) + self.checkProcRegexp.setEnabled(False) + self.checkProcRegexp.setChecked(False) + self.procLine.setText("") + + self.cmdlineCheck.setChecked(False) + self.checkCmdlineRegexp.setEnabled(False) + self.checkCmdlineRegexp.setChecked(False) + self.cmdlineLine.setText("") + + self.uidCheck.setChecked(False) + self.uidLine.setText("") + + self.pidCheck.setChecked(False) + self.pidLine.setText("") + + self.dstPortCheck.setChecked(False) + self.dstPortLine.setText("") + + self.dstIPCheck.setChecked(False) + self.dstIPCombo.setCurrentText("") + + self.dstHostCheck.setChecked(False) + self.dstHostLine.setText("") + + self.selectListButton.setEnabled(False) + self.dstListsCheck.setChecked(False) + self.dstListsLine.setText("") + + self.selectListRegexpButton.setEnabled(False) + self.dstListRegexpCheck.setChecked(False) + self.dstRegexpListsLine.setText("") + + self.selectIPsListButton.setEnabled(False) + self.dstListIPsCheck.setChecked(False) + self.dstListIPsLine.setText("") + + self.selectNetsListButton.setEnabled(False) + self.dstListNetsCheck.setChecked(False) + self.dstListNetsLine.setText("") + + def _load_rule(self, addr=None, rule=None): + if self._load_nodes(addr) == False: + return False + + self.ruleNameEdit.setText(rule.name) + self.enableCheck.setChecked(rule.enabled) + self.precedenceCheck.setChecked(rule.precedence) + if rule.action == Config.ACTION_DENY: + self.actionDenyRadio.setChecked(True) + elif rule.action == Config.ACTION_ALLOW: + self.actionAllowRadio.setChecked(True) + elif rule.action == Config.ACTION_REJECT: + self.actionRejectRadio.setChecked(True) + + self.durationCombo.setCurrentIndex(self._load_duration(self.rule.duration)) + + if self.rule.operator.type != Config.RULE_TYPE_LIST: + self._load_rule_operator(self.rule.operator) + else: + rule_options = json.loads(self.rule.operator.data) + for r in rule_options: + _sensitive = False + if 'sensitive' in r: + _sensitive = r['sensitive'] + + op = ui_pb2.Operator(type=r['type'], operand=r['operand'], data=r['data'], sensitive=_sensitive) + self._load_rule_operator(op) + + return True + + def _load_rule_operator(self, operator): + self.sensitiveCheck.setChecked(operator.sensitive) + if operator.operand == "protocol": + self.protoCheck.setChecked(True) + self.protoCombo.setEnabled(True) + self.protoCombo.setCurrentText(operator.data.upper()) + + if operator.operand == "process.path": + self.procCheck.setChecked(True) + self.procLine.setEnabled(True) + self.procLine.setText(operator.data) + self.checkProcRegexp.setEnabled(True) + self.checkProcRegexp.setChecked(operator.type == Config.RULE_TYPE_REGEXP) + + if operator.operand == "process.command": + self.cmdlineCheck.setChecked(True) + self.cmdlineLine.setEnabled(True) + self.cmdlineLine.setText(operator.data) + self.checkCmdlineRegexp.setEnabled(True) + self.checkCmdlineRegexp.setChecked(operator.type == Config.RULE_TYPE_REGEXP) + + if operator.operand == "user.id": + self.uidCheck.setChecked(True) + self.uidLine.setEnabled(True) + self.uidLine.setText(operator.data) + + if operator.operand == "process.id": + self.pidCheck.setChecked(True) + self.pidLine.setEnabled(True) + self.pidLine.setText(operator.data) + + if operator.operand == "dest.port": + self.dstPortCheck.setChecked(True) + self.dstPortLine.setEnabled(True) + self.dstPortLine.setText(operator.data) + + if operator.operand == "dest.ip" or operator.operand == "dest.network": + self.dstIPCheck.setChecked(True) + self.dstIPCombo.setEnabled(True) + if operator.data == self.LAN_RANGES: + self.dstIPCombo.setCurrentText(self.LAN_LABEL) + else: + self.dstIPCombo.setCurrentText(operator.data) + + if operator.operand == "dest.host": + self.dstHostCheck.setChecked(True) + self.dstHostLine.setEnabled(True) + self.dstHostLine.setText(operator.data) + + if operator.operand == "lists.domains": + self.dstListsCheck.setChecked(True) + self.dstListsCheck.setEnabled(True) + self.dstListsLine.setText(operator.data) + self.selectListButton.setEnabled(True) + + if operator.operand == "lists.domains_regexp": + self.dstListRegexpCheck.setChecked(True) + self.dstListRegexpCheck.setEnabled(True) + self.dstRegexpListsLine.setText(operator.data) + self.selectListRegexpButton.setEnabled(True) + + if operator.operand == "lists.ips": + self.dstListIPsCheck.setChecked(True) + self.dstListIPsCheck.setEnabled(True) + self.dstListIPsLine.setText(operator.data) + self.selectIPsListButton.setEnabled(True) + + if operator.operand == "lists.nets": + self.dstListNetsCheck.setChecked(True) + self.dstListNetsCheck.setEnabled(True) + self.dstListNetsLine.setText(operator.data) + self.selectNetsListButton.setEnabled(True) + + def _load_nodes(self, addr=None): + try: + self.nodesCombo.clear() + self._node_list = self._nodes.get() + + if addr != None and addr not in self._node_list: + Message.ok(QC.translate("rules", "<b>Error loading rule</b>"), + QC.translate("rules", "node {0} not connected".format(addr)), + QtWidgets.QMessageBox.Warning) + return False + + if len(self._node_list) < 2: + self.nodeApplyAllCheck.setVisible(False) + + for node in self._node_list: + self.nodesCombo.addItem(node) + + if addr != None: + self.nodesCombo.setCurrentText(addr) + + except Exception as e: + print(self.LOG_TAG, "exception loading nodes: ", e, addr) + return False + + return True + + def _insert_rule_to_db(self, node_addr): + self._db.insert("rules", + "(time, node, name, enabled, precedence, action, duration, operator_type, operator_sensitive, operator_operand, operator_data)", + (datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + node_addr, self.rule.name, + str(self.rule.enabled), str(self.rule.precedence), + self.rule.action, self.rule.duration, self.rule.operator.type, + str(self.rule.operator.sensitive), self.rule.operator.operand, self.rule.operator.data), + action_on_conflict="REPLACE") + + def _add_rule(self): + try: + if self.nodeApplyAllCheck.isChecked(): + for pos in range(self.nodesCombo.count()): + self._insert_rule_to_db(self.nodesCombo.itemText(pos)) + else: + self._insert_rule_to_db(self.nodesCombo.currentText()) + + notif = ui_pb2.Notification( + id=int(str(time.time()).replace(".", "")), + type=ui_pb2.CHANGE_RULE, + data="", + rules=[self.rule]) + if self.nodeApplyAllCheck.isChecked(): + nid = self._nodes.send_notifications(notif, self._notification_callback) + else: + nid = self._nodes.send_notification(self.nodesCombo.currentText(), notif, self._notification_callback) + + self._notifications_sent[nid] = notif + except Exception as e: + print(self.LOG_TAG, "add_rule() exception: ", e) + + def _delete_rule(self): + try: + # if the rule name has changed, we need to remove the old one + if self._old_rule_name != self.rule.name: + node = self.nodesCombo.currentText() + old_rule = self.rule + old_rule.name = self._old_rule_name + if self.nodeApplyAllCheck.isChecked(): + nid, noti = self._nodes.delete_rule(rule_name=self._old_rule_name, addr=None, callback=self._notification_callback) + self._notifications_sent[nid] = noti + else: + nid, noti = self._nodes.delete_rule(self._old_rule_name, node, self._notification_callback) + self._notifications_sent[nid] = noti + + except Exception as e: + print(self.LOG_TAG, "delete_rule() exception: ", e) + + + def _save_rule(self): + """ + Create a new rule based on the fields selected. + + Ensure that some constraints are met: + - Determine if a field can be a regexp. + - Validate regexp. + - Fields cannot be empty. + - If the user has not provided a rule name, auto assign one. + """ + self.rule = ui_pb2.Rule() + self.rule.name = self.ruleNameEdit.text() + self.rule.enabled = self.enableCheck.isChecked() + self.rule.precedence = self.precedenceCheck.isChecked() + self.rule.operator.type = Config.RULE_TYPE_SIMPLE + self.rule.action = Config.ACTION_DENY + if self.actionAllowRadio.isChecked(): + self.rule.action = Config.ACTION_ALLOW + elif self.actionRejectRadio.isChecked(): + self.rule.action = Config.ACTION_REJECT + + self.rule.duration = self._get_duration(self.durationCombo.currentIndex()) + + # FIXME: there should be a sensitive checkbox per operand + self.rule.operator.sensitive = self.sensitiveCheck.isChecked() + rule_data = [] + if self.protoCheck.isChecked(): + if self.protoCombo.currentText() == "": + return False, QC.translate("rules", "protocol can not be empty, or uncheck it") + + self.rule.operator.operand = "protocol" + self.rule.operator.data = self.protoCombo.currentText() + rule_data.append( + { + "type": Config.RULE_TYPE_SIMPLE, + "operand": "protocol", + "data": self.protoCombo.currentText().lower(), + "sensitive": self.sensitiveCheck.isChecked() + }) + if self._is_regex(self.protoCombo.currentText()): + rule_data[len(rule_data)-1]['type'] = Config.RULE_TYPE_REGEXP + if self._is_valid_regex(self.protoCombo.currentText()) == False: + return False, QC.translate("rules", "Protocol regexp error") + + if self.procCheck.isChecked(): + if self.procLine.text() == "": + return False, QC.translate("rules", "process path can not be empty") + + self.rule.operator.operand = "process.path" + self.rule.operator.data = self.procLine.text() + rule_data.append( + { + "type": Config.RULE_TYPE_SIMPLE, + "operand": "process.path", + "data": self.procLine.text(), + "sensitive": self.sensitiveCheck.isChecked() + }) + if self.checkProcRegexp.isChecked(): + rule_data[len(rule_data)-1]['type'] = Config.RULE_TYPE_REGEXP + if self._is_valid_regex(self.procLine.text()) == False: + return False, QC.translate("rules", "Process path regexp error") + + if self.cmdlineCheck.isChecked(): + if self.cmdlineLine.text() == "": + return False, QC.translate("rules", "command line can not be empty") + + self.rule.operator.operand = "process.command" + self.rule.operator.data = self.cmdlineLine.text() + rule_data.append( + { + 'type': Config.RULE_TYPE_SIMPLE, + 'operand': 'process.command', + 'data': self.cmdlineLine.text(), + "sensitive": self.sensitiveCheck.isChecked() + }) + if self.checkCmdlineRegexp.isChecked(): + rule_data[len(rule_data)-1]['type'] = Config.RULE_TYPE_REGEXP + if self._is_valid_regex(self.cmdlineLine.text()) == False: + return False, QC.translate("rules", "Command line regexp error") + + if self.dstPortCheck.isChecked(): + if self.dstPortLine.text() == "": + return False, QC.translate("rules", "Dest port can not be empty") + + self.rule.operator.operand = "dest.port" + self.rule.operator.data = self.dstPortLine.text() + rule_data.append( + { + 'type': Config.RULE_TYPE_SIMPLE, + 'operand': 'dest.port', + 'data': self.dstPortLine.text(), + "sensitive": self.sensitiveCheck.isChecked() + }) + if self._is_regex(self.dstPortLine.text()): + rule_data[len(rule_data)-1]['type'] = Config.RULE_TYPE_REGEXP + if self._is_valid_regex(self.dstPortLine.text()) == False: + return False, QC.translate("rules", "Dst port regexp error") + + if self.dstHostCheck.isChecked(): + if self.dstHostLine.text() == "": + return False, QC.translate("rules", "Dest host can not be empty") + + self.rule.operator.operand = "dest.host" + self.rule.operator.data = self.dstHostLine.text() + rule_data.append( + { + 'type': Config.RULE_TYPE_SIMPLE, + 'operand': 'dest.host', + 'data': self.dstHostLine.text(), + "sensitive": self.sensitiveCheck.isChecked() + }) + if self._is_regex(self.dstHostLine.text()): + rule_data[len(rule_data)-1]['type'] = Config.RULE_TYPE_REGEXP + if self._is_valid_regex(self.dstHostLine.text()) == False: + return False, QC.translate("rules", "Dst host regexp error") + + if self.dstIPCheck.isChecked(): + if self.dstIPCombo.currentText() == "": + return False, QC.translate("rules", "Dest IP/Network can not be empty") + + dstIPtext = self.dstIPCombo.currentText() + + if dstIPtext == self.LAN_LABEL: + self.rule.operator.operand = "dest.ip" + self.rule.operator.type = Config.RULE_TYPE_REGEXP + dstIPtext = self.LAN_RANGES + else: + try: + if type(ipaddress.ip_address(self.dstIPCombo.currentText())) == ipaddress.IPv4Address \ + or type(ipaddress.ip_address(self.dstIPCombo.currentText())) == ipaddress.IPv6Address: + self.rule.operator.operand = "dest.ip" + self.rule.operator.type = Config.RULE_TYPE_SIMPLE + except Exception: + self.rule.operator.operand = "dest.network" + self.rule.operator.type = Config.RULE_TYPE_NETWORK + + if self._is_regex(dstIPtext): + self.rule.operator.operand = "dest.ip" + self.rule.operator.type = Config.RULE_TYPE_REGEXP + if self._is_valid_regex(self.dstIPCombo.currentText()) == False: + return False, QC.translate("rules", "Dst IP regexp error") + + rule_data.append( + { + 'type': self.rule.operator.type, + 'operand': self.rule.operator.operand, + 'data': dstIPtext, + "sensitive": self.sensitiveCheck.isChecked() + }) + + if self.uidCheck.isChecked(): + if self.uidLine.text() == "": + return False, QC.translate("rules", "User ID can not be empty") + + self.rule.operator.operand = "user.id" + self.rule.operator.data = self.uidLine.text() + rule_data.append( + { + 'type': Config.RULE_TYPE_SIMPLE, + 'operand': 'user.id', + 'data': self.uidLine.text(), + "sensitive": self.sensitiveCheck.isChecked() + }) + if self._is_regex(self.uidLine.text()): + rule_data[len(rule_data)-1]['type'] = Config.RULE_TYPE_REGEXP + if self._is_valid_regex(self.uidLine.text()) == False: + return False, QC.translate("rules", "User ID regexp error") + + if self.pidCheck.isChecked(): + if self.pidLine.text() == "": + return False, QC.translate("rules", "PID field can not be empty") + + self.rule.operator.operand = "process.id" + self.rule.operator.data = self.pidLine.text() + rule_data.append( + { + 'type': Config.RULE_TYPE_SIMPLE, + 'operand': 'process.id', + 'data': self.pidLine.text(), + "sensitive": self.sensitiveCheck.isChecked() + }) + if self._is_regex(self.pidLine.text()): + rule_data[len(rule_data)-1]['type'] = Config.RULE_TYPE_REGEXP + if self._is_valid_regex(self.pidLine.text()) == False: + return False, QC.translate("rules", "PID field regexp error") + + if self.dstListsCheck.isChecked(): + if self.dstListsLine.text() == "": + return False, QC.translate("rules", "Lists field cannot be empty") + if os.path.isdir(self.dstListsLine.text()) == False: + return False, QC.translate("rules", "Lists field must be a directory") + + self.rule.operator.type = Config.RULE_TYPE_LISTS + self.rule.operator.operand = "lists.domains" + rule_data.append( + { + 'type': Config.RULE_TYPE_LISTS, + 'operand': 'lists.domains', + 'data': self.dstListsLine.text(), + 'sensitive': self.sensitiveCheck.isChecked() + }) + self.rule.operator.data = json.dumps(rule_data) + + if self.dstListRegexpCheck.isChecked(): + if self.dstRegexpListsLine.text() == "": + return False, QC.translate("rules", "Lists field cannot be empty") + if os.path.isdir(self.dstRegexpListsLine.text()) == False: + return False, QC.translate("rules", "Lists field must be a directory") + + self.rule.operator.type = Config.RULE_TYPE_LISTS + self.rule.operator.operand = "lists.domains_regexp" + rule_data.append( + { + 'type': Config.RULE_TYPE_LISTS, + 'operand': 'lists.domains_regexp', + 'data': self.dstRegexpListsLine.text(), + 'sensitive': self.sensitiveCheck.isChecked() + }) + self.rule.operator.data = json.dumps(rule_data) + + if self.dstListNetsCheck.isChecked(): + if self.dstListNetsLine.text() == "": + return False, QC.translate("rules", "Lists field cannot be empty") + if os.path.isdir(self.dstListNetsLine.text()) == False: + return False, QC.translate("rules", "Lists field must be a directory") + + self.rule.operator.type = Config.RULE_TYPE_LISTS + self.rule.operator.operand = "lists.nets" + rule_data.append( + { + 'type': Config.RULE_TYPE_LISTS, + 'operand': 'lists.nets', + 'data': self.dstListNetsLine.text(), + 'sensitive': self.sensitiveCheck.isChecked() + }) + self.rule.operator.data = json.dumps(rule_data) + + + if self.dstListIPsCheck.isChecked(): + if self.dstListIPsLine.text() == "": + return False, QC.translate("rules", "Lists field cannot be empty") + if os.path.isdir(self.dstListIPsLine.text()) == False: + return False, QC.translate("rules", "Lists field must be a directory") + + self.rule.operator.type = Config.RULE_TYPE_LISTS + self.rule.operator.operand = "lists.ips" + rule_data.append( + { + 'type': Config.RULE_TYPE_LISTS, + 'operand': 'lists.ips', + 'data': self.dstListIPsLine.text(), + 'sensitive': self.sensitiveCheck.isChecked() + }) + self.rule.operator.data = json.dumps(rule_data) + + if len(rule_data) >= 2: + self.rule.operator.type = Config.RULE_TYPE_LIST + self.rule.operator.operand = Config.RULE_TYPE_LIST + self.rule.operator.data = json.dumps(rule_data) + + elif len(rule_data) == 1: + self.rule.operator.operand = rule_data[0]['operand'] + self.rule.operator.data = rule_data[0]['data'] + if self.checkProcRegexp.isChecked(): + self.rule.operator.type = Config.RULE_TYPE_REGEXP + elif self.checkCmdlineRegexp.isChecked(): + self.rule.operator.type = Config.RULE_TYPE_REGEXP + elif (self.procCheck.isChecked() == False and self.cmdlineCheck.isChecked() == False) \ + and self._is_regex(self.rule.operator.data): + self.rule.operator.type = Config.RULE_TYPE_REGEXP + + else: + return False, QC.translate("rules", "Select at least one field.") + + if self.ruleNameEdit.text() == "": + self.rule.name = slugify("%s %s %s" % (self.rule.action, self.rule.operator.type, self.rule.operator.data)) + + return True, "" + + def edit_rule(self, records, _addr=None): + self.WORK_MODE = self.EDIT_RULE + self._reset_state() + + self.rule = self.get_rule_from_records(records) + if self.rule.operator.type not in Config.RulesTypes: + Message.ok(QC.translate("rules", "<b>Rule not supported</b>"), + QC.translate("rules", "This type of rule ({0}) is not supported by version {1}".format(self.rule.operator.type, version)), + QtWidgets.QMessageBox.Warning) + self.hide() + return + + self._old_rule_name = records.value(2) + + if self._load_rule(addr=_addr, rule=self.rule): + self.show() + + def new_rule(self): + self.WORK_MODE = self.ADD_RULE + self._reset_state() + self._load_nodes() + self.show() diff --git a/ui/opensnitch/dialogs/stats.py b/ui/opensnitch/dialogs/stats.py new file mode 100644 index 0000000..c8b3a2d --- /dev/null +++ b/ui/opensnitch/dialogs/stats.py @@ -0,0 +1,2041 @@ +import threading +import datetime +import sys +import os +import csv +import io + +from PyQt5 import QtCore, QtGui, uic, QtWidgets +from PyQt5.QtCore import QCoreApplication as QC + +from opensnitch import ui_pb2 +from opensnitch.config import Config +from opensnitch.version import version +from opensnitch.nodes import Nodes +from opensnitch.dialogs.preferences import PreferencesDialog +from opensnitch.dialogs.ruleseditor import RulesEditorDialog +from opensnitch.dialogs.processdetails import ProcessDetailsDialog +from opensnitch.customwidgets.main import ColorizedDelegate, ConnectionsTableModel +from opensnitch.customwidgets.generictableview import GenericTableModel +from opensnitch.customwidgets.addresstablemodel import AddressTableModel +from opensnitch.utils import Message, QuickHelp, AsnDB + +DIALOG_UI_PATH = "%s/../res/stats.ui" % os.path.dirname(sys.modules[__name__].__file__) +class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): + RED = QtGui.QColor(0xff, 0x63, 0x47) + GREEN = QtGui.QColor(0x2e, 0x90, 0x59) + PURPLE = QtGui.QColor(0x7f, 0x00, 0xff) + + _trigger = QtCore.pyqtSignal(bool, bool) + settings_saved = QtCore.pyqtSignal() + _status_changed_trigger = QtCore.pyqtSignal(bool) + _shown_trigger = QtCore.pyqtSignal() + _notification_trigger = QtCore.pyqtSignal(ui_pb2.Notification) + _notification_callback = QtCore.pyqtSignal(ui_pb2.NotificationReply) + + SORT_ORDER = ["ASC", "DESC"] + LIMITS = ["LIMIT 50", "LIMIT 100", "LIMIT 200", "LIMIT 300", ""] + LAST_GROUP_BY = "" + + # general + COL_TIME = 0 + COL_NODE = 1 + COL_ACTION = 2 + COL_DSTIP = 3 + COL_PROTO = 4 + COL_PROCS = 5 + COL_RULES = 6 + GENERAL_COL_NUM = 7 + + # stats + COL_WHAT = 0 + + # rules + COL_R_NODE = 1 + COL_R_NAME = 2 + COL_R_ENABLED = 3 + COL_R_ACTION = 4 + COL_R_DURATION = 5 + COL_R_OP_TYPE = 6 + COL_R_OP_OPERAND = 7 + + # procs + COL_PID = 6 + + TAB_MAIN = 0 + TAB_NODES = 1 + TAB_RULES = 2 + TAB_HOSTS = 3 + TAB_PROCS = 4 + TAB_ADDRS = 5 + TAB_PORTS = 6 + TAB_USERS = 7 + + # row of entries + RULES_TREE_APPS = 0 + RULES_TREE_NODES = 1 + RULES_TREE_PERMANENT = 0 + RULES_TREE_TEMPORARY = 1 + + RULES_COMBO_PERMANENT = 1 + RULES_COMBO_TEMPORARY = 2 + + RULES_TYPE_PERMANENT = 0 + RULES_TYPE_TEMPORARY = 1 + + FILTER_TREE_APPS = 0 + FILTER_TREE_NODES = 3 + + # FIXME: don't translate, used only for default argument on _update_status_label + FIREWALL_DISABLED = "Disabled" + + # if the user clicks on an item of a table, it'll enter into the detail + # view. From there, deny further clicks on the items. + IN_DETAIL_VIEW = { + TAB_MAIN: False, + TAB_NODES: False, + TAB_RULES: False, + TAB_HOSTS: False, + TAB_PROCS: False, + TAB_ADDRS: False, + TAB_PORTS: False, + TAB_USERS: False + } + # restore scrollbar position when going back from a detail view + LAST_SCROLL_VALUE = None + # try to restore last selection + LAST_SELECTED_ITEM = "" + + commonDelegateConf = { + Config.ACTION_DENY: RED, + Config.ACTION_REJECT: PURPLE, + Config.ACTION_ALLOW: GREEN, + 'alignment': QtCore.Qt.AlignCenter | QtCore.Qt.AlignHCenter + } + + commonTableConf = { + "name": "", + "label": None, + "cmd": None, + "view": None, + "model": None, + "delegate": commonDelegateConf, + "display_fields": "*" + } + + TABLES = { + TAB_MAIN: { + "name": "connections", + "label": None, + "cmd": None, + "cmdCleanStats": None, + "view": None, + "filterLine": None, + "model": None, + "delegate": commonDelegateConf, + "display_fields": "time as Time, " \ + "node as Node, " \ + "action as Action, " \ + "CASE dst_host WHEN ''" \ + " THEN dst_ip || ' -> ' || dst_port " \ + " ELSE dst_host || ' -> ' || dst_port " \ + "END Destination, " \ + "protocol as Protocol, " \ + "process as Process, " \ + "rule as Rule", + "group_by": LAST_GROUP_BY, + "last_order_by": "1", + "last_order_to": 1, + "rows_selected": False + }, + TAB_NODES: { + "name": "nodes", + "label": None, + "cmd": None, + "cmdCleanStats": None, + "view": None, + "filterLine": None, + "model": None, + "delegate": { + Config.ACTION_DENY: RED, + Config.ACTION_REJECT: PURPLE, + Config.ACTION_ALLOW: GREEN, + Nodes.OFFLINE: RED, + Nodes.ONLINE: GREEN, + 'alignment': QtCore.Qt.AlignCenter | QtCore.Qt.AlignHCenter + }, + "display_fields": "last_connection as LastConnection, "\ + "addr as Addr, " \ + "status as Status, " \ + "hostname as Hostname, " \ + "daemon_version as Version, " \ + "daemon_uptime as Uptime, " \ + "daemon_rules as Rules," \ + "cons as Connections," \ + "cons_dropped as Dropped," \ + "version as Version", + "header_labels": [], + "last_order_by": "1", + "last_order_to": 1, + "rows_selected": False + }, + TAB_RULES: { + "name": "rules", + "label": None, + "cmd": None, + "cmdCleanStats": None, + "view": None, + "filterLine": None, + "model": None, + "delegate": commonDelegateConf, + "display_fields": "*", + "header_labels": [], + "last_order_by": "2", + "last_order_to": 0, + "rows_selected": False + }, + TAB_HOSTS: { + "name": "hosts", + "label": None, + "cmd": None, + "cmdCleanStats": None, + "view": None, + "filterLine": None, + "model": None, + "delegate": commonDelegateConf, + "display_fields": "*", + "header_labels": [], + "last_order_by": "2", + "last_order_to": 1, + "rows_selected": False + }, + TAB_PROCS: { + "name": "procs", + "label": None, + "cmd": None, + "cmdCleanStats": None, + "view": None, + "filterLine": None, + "model": None, + "delegate": commonDelegateConf, + "display_fields": "*", + "header_labels": [], + "last_order_by": "2", + "last_order_to": 1, + "rows_selected": False + }, + TAB_ADDRS: { + "name": "addrs", + "label": None, + "cmd": None, + "cmdCleanStats": None, + "view": None, + "filterLine": None, + "model": None, + "delegate": commonDelegateConf, + "display_fields": "*", + "header_labels": [], + "last_order_by": "2", + "last_order_to": 1, + "rows_selected": False + }, + TAB_PORTS: { + "name": "ports", + "label": None, + "cmd": None, + "cmdCleanStats": None, + "view": None, + "filterLine": None, + "model": None, + "delegate": commonDelegateConf, + "display_fields": "*", + "header_labels": [], + "last_order_by": "2", + "last_order_to": 1, + "rows_selected": False + }, + TAB_USERS: { + "name": "users", + "label": None, + "cmd": None, + "cmdCleanStats": None, + "view": None, + "filterLine": None, + "model": None, + "delegate": commonDelegateConf, + "display_fields": "*", + "header_labels": [], + "last_order_by": "2", + "last_order_to": 1, + "rows_selected": False + } + } + + def __init__(self, parent=None, address=None, db=None, dbname="db", appicon=None): + super(StatsDialog, self).__init__(parent) + QtWidgets.QDialog.__init__(self, parent, QtCore.Qt.WindowStaysOnTopHint) + + self._current_desktop = os.environ['XDG_CURRENT_DESKTOP'] if os.environ.get("XDG_CURRENT_DESKTOP") != None else None + + self.setWindowFlags(QtCore.Qt.Window) + self.setupUi(self) + self.setWindowIcon(appicon) + + # columns names. Must be added here in order to names be translated. + self.COL_STR_NAME = QC.translate("stats", "Name", "This is a word, without spaces and symbols.").replace(" ", "") + self.COL_STR_ADDR = QC.translate("stats", "Address", "This is a word, without spaces and symbols.").replace(" ", "") + self.COL_STR_STATUS = QC.translate("stats", "Status", "This is a word, without spaces and symbols.").replace(" ", "") + self.COL_STR_HOSTNAME = QC.translate("stats", "Hostname", "This is a word, without spaces and symbols.").replace(" ", "") + self.COL_STR_UPTIME = QC.translate("stats", "Uptime", "This is a word, without spaces and symbols.").replace(" ", "") + self.COL_STR_VERSION = QC.translate("stats", "Version", "This is a word, without spaces and symbols.").replace(" ", "") + self.COL_STR_RULES_NUM = QC.translate("stats", "Rules", "This is a word, without spaces and symbols.").replace(" ", "") + self.COL_STR_TIME = QC.translate("stats", "Time", "This is a word, without spaces and symbols.").replace(" ", "") + self.COL_STR_ACTION = QC.translate("stats", "Action", "This is a word, without spaces and symbols.").replace(" ", "") + self.COL_STR_DURATION = QC.translate("stats", "Duration", "This is a word, without spaces and symbols.").replace(" ", "") + self.COL_STR_NODE = QC.translate("stats", "Node", "This is a word, without spaces and symbols.").replace(" ", "") + self.COL_STR_ENABLED = QC.translate("stats", "Enabled", "This is a word, without spaces and symbols.").replace(" ", "") + self.COL_STR_PRECEDENCE = QC.translate("stats", "Precedence", "This is a word, without spaces and symbols.").replace(" ", "") + self.COL_STR_HITS = QC.translate("stats", "Hits", "This is a word, without spaces and symbols.").replace(" ", "") + self.COL_STR_PROTOCOL = QC.translate("stats", "Protocol", "This is a word, without spaces and symbols.").replace(" ", "") + self.COL_STR_PROCESS = QC.translate("stats", "Process", "This is a word, without spaces and symbols.").replace(" ", "") + self.COL_STR_PROC_ARGS = QC.translate("stats", "Args", "This is a word, without spaces and symbols.").replace(" ", "") + self.COL_STR_DESTINATION = QC.translate("stats", "Destination", "This is a word, without spaces and symbols.").replace(" ", "") + self.COL_STR_DST_IP = QC.translate("stats", "DstIP", "This is a word, without spaces and symbols.").replace(" ", "") + self.COL_STR_DST_HOST = QC.translate("stats", "DstHost", "This is a word, without spaces and symbols.").replace(" ", "") + self.COL_STR_DST_PORT = QC.translate("stats", "DstPort", "This is a word, without spaces and symbols.").replace(" ", "") + self.COL_STR_RULE = QC.translate("stats", "Rule", "This is a word, without spaces and symbols.").replace(" ", "") + self.COL_STR_UID = QC.translate("stats", "UserID", "This is a word, without spaces and symbols.").replace(" ", "") + self.COL_STR_LAST_CONNECTION = QC.translate("stats", "LastConnection", "This is a word, without spaces and symbols.").replace(" ", "") + + self.FIREWALL_STOPPED = QC.translate("stats", "Not running") + self.FIREWALL_DISABLED = QC.translate("stats", "Disabled") + self.FIREWALL_RUNNING = QC.translate("stats", "Running") + + self._db = db + self._db_sqlite = self._db.get_db() + self._db_name = dbname + + self.asndb = AsnDB.instance() + + self._cfg = Config.get() + self._nodes = Nodes.instance() + + # TODO: allow to display multiples dialogs + self._proc_details_dialog = ProcessDetailsDialog(appicon=appicon) + # TODO: allow to navigate records by offsets + self.prevButton.setVisible(False) + self.nextButton.setVisible(False) + + self.daemon_connected = False + # skip table updates if a context menu is active + self._context_menu_active = False + # used to skip updates while the user is moving the scrollbar + self.scrollbar_active = False + + self._lock = threading.RLock() + self._address = address + self._stats = None + self._notifications_sent = {} + + self._prefs_dialog = PreferencesDialog(appicon=appicon) + self._rules_dialog = RulesEditorDialog(appicon=appicon) + self._prefs_dialog.saved.connect(self._on_settings_saved) + self._trigger.connect(self._on_update_triggered) + self._notification_callback.connect(self._cb_notification_callback) + + self.nodeLabel.setText("") + self.nodeLabel.setStyleSheet('color: green;font-size:12pt; font-weight:600;') + self.rulesSplitter.setStretchFactor(0,0) + self.rulesSplitter.setStretchFactor(1,2) + + self.startButton.clicked.connect(self._cb_start_clicked) + self.prefsButton.clicked.connect(self._cb_prefs_clicked) + self.saveButton.clicked.connect(self._on_save_clicked) + self.comboAction.currentIndexChanged.connect(self._cb_combo_action_changed) + self.limitCombo.currentIndexChanged.connect(self._cb_limit_combo_changed) + self.tabWidget.currentChanged.connect(self._cb_tab_changed) + self.delRuleButton.clicked.connect(self._cb_del_rule_clicked) + self.rulesSplitter.splitterMoved.connect(self._cb_rules_splitter_moved) + self.rulesTreePanel.itemClicked.connect(self._cb_rules_tree_item_clicked) + self.enableRuleCheck.clicked.connect(self._cb_enable_rule_toggled) + self.editRuleButton.clicked.connect(self._cb_edit_rule_clicked) + self.newRuleButton.clicked.connect(self._cb_new_rule_clicked) + self.cmdProcDetails.clicked.connect(self._cb_proc_details_clicked) + self.comboRulesFilter.currentIndexChanged.connect(self._cb_rules_filter_combo_changed) + self.helpButton.clicked.connect(self._cb_help_button_clicked) + self.nextButton.clicked.connect(self._cb_next_button_clicked) + self.prevButton.clicked.connect(self._cb_prev_button_clicked) + + self.enableRuleCheck.setVisible(False) + self.delRuleButton.setVisible(False) + self.editRuleButton.setVisible(False) + self.nodeRuleLabel.setVisible(False) + self.comboRulesFilter.setVisible(False) + + # translations must be done here, otherwise they don't take effect + self.TABLES[self.TAB_NODES]['header_labels'] = [ + self.COL_STR_LAST_CONNECTION, + self.COL_STR_ADDR, + self.COL_STR_STATUS, + self.COL_STR_HOSTNAME, + self.COL_STR_VERSION, + self.COL_STR_UPTIME, + QC.translate("stats", "Rules", "This is a word, without spaces and symbols.").replace(" ", ""), + QC.translate("stats", "Connections", "This is a word, without spaces and symbols.").replace(" ", ""), + QC.translate("stats", "Dropped", "This is a word, without spaces and symbols.").replace(" ", ""), + QC.translate("stats", "Version", "This is a word, without spaces and symbols.").replace(" ", ""), + ] + + self.TABLES[self.TAB_RULES]['header_labels'] = [ + self.COL_STR_TIME, + self.COL_STR_NODE, + self.COL_STR_NAME, + self.COL_STR_ENABLED, + self.COL_STR_PRECEDENCE, + self.COL_STR_ACTION, + self.COL_STR_DURATION, + "operator_type", + "operator_sensitive", + "operator_operand", + "operator_data", + ] + + stats_headers = [ + QC.translate("stats", "What", "This is a word, without spaces and symbols.").replace(" ", ""), + QC.translate("stats", "Hits", "This is a word, without spaces and symbols.").replace(" ", ""), + ] + + self.TABLES[self.TAB_HOSTS]['header_labels'] = stats_headers + self.TABLES[self.TAB_PROCS]['header_labels'] = stats_headers + self.TABLES[self.TAB_ADDRS]['header_labels'] = stats_headers + self.TABLES[self.TAB_USERS]['header_labels'] = stats_headers + + self.TABLES[self.TAB_MAIN]['view'] = self._setup_table(QtWidgets.QTableView, self.eventsTable, "connections", + self.TABLES[self.TAB_MAIN]['display_fields'], + order_by="1", + group_by=self.TABLES[self.TAB_MAIN]['group_by'], + delegate=self.TABLES[self.TAB_MAIN]['delegate'], + resize_cols=(), + model=GenericTableModel("connections", [ + self.COL_STR_TIME, + self.COL_STR_NODE, + self.COL_STR_ACTION, + self.COL_STR_DESTINATION, + self.COL_STR_PROTOCOL, + self.COL_STR_PROCESS, + self.COL_STR_RULE, + ]), + verticalScrollBar=self.connectionsTableScrollBar, + limit=self._get_limit() + ) + self.TABLES[self.TAB_NODES]['view'] = self._setup_table(QtWidgets.QTableView, self.nodesTable, "nodes", + self.TABLES[self.TAB_NODES]['display_fields'], + order_by="3,2,1", + resize_cols=(self.COL_NODE,), + model=GenericTableModel("nodes", self.TABLES[self.TAB_NODES]['header_labels']), + verticalScrollBar=self.verticalScrollBar, + sort_direction=self.SORT_ORDER[1], + delegate=self.TABLES[self.TAB_NODES]['delegate']) + self.TABLES[self.TAB_RULES]['view'] = self._setup_table(QtWidgets.QTableView, + self.rulesTable, "rules", + model=GenericTableModel("rules", self.TABLES[self.TAB_RULES]['header_labels']), + verticalScrollBar=self.rulesScrollBar, + delegate=self.TABLES[self.TAB_RULES]['delegate'], + order_by="2", + sort_direction=self.SORT_ORDER[0]) + self.TABLES[self.TAB_HOSTS]['view'] = self._setup_table(QtWidgets.QTableView, + self.hostsTable, "hosts", + model=GenericTableModel("hosts", self.TABLES[self.TAB_HOSTS]['header_labels']), + verticalScrollBar=self.hostsScrollBar, + resize_cols=(self.COL_WHAT,), + delegate=self.TABLES[self.TAB_HOSTS]['delegate'], + order_by="2", + limit=self._get_limit() + ) + self.TABLES[self.TAB_PROCS]['view'] = self._setup_table(QtWidgets.QTableView, + self.procsTable, "procs", + model=GenericTableModel("procs", self.TABLES[self.TAB_PROCS]['header_labels']), + verticalScrollBar=self.procsScrollBar, + resize_cols=(self.COL_WHAT,), + delegate=self.TABLES[self.TAB_PROCS]['delegate'], + order_by="2", + limit=self._get_limit() + ) + self.TABLES[self.TAB_ADDRS]['view'] = self._setup_table(QtWidgets.QTableView, + self.addrTable, "addrs", + model=AddressTableModel("addrs", self.TABLES[self.TAB_ADDRS]['header_labels']), + verticalScrollBar=self.addrsScrollBar, + resize_cols=(self.COL_WHAT,), + delegate=self.TABLES[self.TAB_ADDRS]['delegate'], + order_by="2", + limit=self._get_limit() + ) + self.TABLES[self.TAB_PORTS]['view'] = self._setup_table(QtWidgets.QTableView, + self.portsTable, "ports", + model=GenericTableModel("ports", self.TABLES[self.TAB_PORTS]['header_labels']), + verticalScrollBar=self.portsScrollBar, + resize_cols=(self.COL_WHAT,), + delegate=self.TABLES[self.TAB_PORTS]['delegate'], + order_by="2", + limit=self._get_limit() + ) + self.TABLES[self.TAB_USERS]['view'] = self._setup_table(QtWidgets.QTableView, + self.usersTable, "users", + model=GenericTableModel("users", self.TABLES[self.TAB_USERS]['header_labels']), + verticalScrollBar=self.usersScrollBar, + resize_cols=(self.COL_WHAT,), + delegate=self.TABLES[self.TAB_USERS]['delegate'], + order_by="2", + limit=self._get_limit() + ) + + self.TABLES[self.TAB_NODES]['label'] = self.nodesLabel + self.TABLES[self.TAB_RULES]['label'] = self.ruleLabel + self.TABLES[self.TAB_HOSTS]['label'] = self.hostsLabel + self.TABLES[self.TAB_PROCS]['label'] = self.procsLabel + self.TABLES[self.TAB_ADDRS]['label'] = self.addrsLabel + self.TABLES[self.TAB_PORTS]['label'] = self.portsLabel + self.TABLES[self.TAB_USERS]['label'] = self.usersLabel + + self.TABLES[self.TAB_NODES]['cmd'] = self.cmdNodesBack + self.TABLES[self.TAB_RULES]['cmd'] = self.cmdRulesBack + self.TABLES[self.TAB_HOSTS]['cmd'] = self.cmdHostsBack + self.TABLES[self.TAB_PROCS]['cmd'] = self.cmdProcsBack + self.TABLES[self.TAB_ADDRS]['cmd'] = self.cmdAddrsBack + self.TABLES[self.TAB_PORTS]['cmd'] = self.cmdPortsBack + self.TABLES[self.TAB_USERS]['cmd'] = self.cmdUsersBack + + self.TABLES[self.TAB_MAIN]['cmdCleanStats'] = self.cmdCleanSql + self.TABLES[self.TAB_NODES]['cmdCleanStats'] = self.cmdCleanSql + self.TABLES[self.TAB_RULES]['cmdCleanStats'] = self.cmdCleanSql + self.TABLES[self.TAB_HOSTS]['cmdCleanStats'] = self.cmdCleanSql + self.TABLES[self.TAB_PROCS]['cmdCleanStats'] = self.cmdCleanSql + self.TABLES[self.TAB_ADDRS]['cmdCleanStats'] = self.cmdCleanSql + self.TABLES[self.TAB_PORTS]['cmdCleanStats'] = self.cmdCleanSql + self.TABLES[self.TAB_USERS]['cmdCleanStats'] = self.cmdCleanSql + # the rules clean button is only for a particular rule, not all. + self.TABLES[self.TAB_RULES]['cmdCleanStats'].setVisible(False) + self.TABLES[self.TAB_NODES]['cmdCleanStats'].setVisible(False) + self.TABLES[self.TAB_MAIN]['cmdCleanStats'].clicked.connect(lambda: self._cb_clean_sql_clicked(self.TAB_MAIN)) + + self.TABLES[self.TAB_MAIN]['filterLine'] = self.filterLine + self.TABLES[self.TAB_MAIN]['view'].doubleClicked.connect(self._cb_main_table_double_clicked) + self.TABLES[self.TAB_MAIN]['view'].installEventFilter(self) + self.TABLES[self.TAB_MAIN]['filterLine'].textChanged.connect(self._cb_events_filter_line_changed) + + self.TABLES[self.TAB_RULES]['view'].setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.TABLES[self.TAB_RULES]['view'].customContextMenuRequested.connect(self._cb_table_context_menu) + for idx in range(1,8): + self.TABLES[idx]['cmd'].hide() + self.TABLES[idx]['cmd'].setVisible(False) + self.TABLES[idx]['cmd'].clicked.connect(lambda: self._cb_cmd_back_clicked(idx)) + if self.TABLES[idx]['cmdCleanStats'] != None: + self.TABLES[idx]['cmdCleanStats'].clicked.connect(lambda: self._cb_clean_sql_clicked(idx)) + self.TABLES[idx]['label'].setStyleSheet('color: blue; font-size:9pt; font-weight:600;') + self.TABLES[idx]['label'].setVisible(False) + self.TABLES[idx]['view'].doubleClicked.connect(self._cb_table_double_clicked) + self.TABLES[idx]['view'].selectionModel().selectionChanged.connect(self._cb_table_selection_changed) + self.TABLES[idx]['view'].installEventFilter(self) + + self._load_settings() + + self._tables = ( \ + self.TABLES[self.TAB_MAIN]['view'], + self.TABLES[self.TAB_NODES]['view'], + self.TABLES[self.TAB_RULES]['view'], + self.TABLES[self.TAB_HOSTS]['view'], + self.TABLES[self.TAB_PROCS]['view'], + self.TABLES[self.TAB_ADDRS]['view'], + self.TABLES[self.TAB_PORTS]['view'], + self.TABLES[self.TAB_USERS]['view'] + ) + self._file_names = ( \ + 'events.csv', + 'nodes.csv', + 'rules.csv', + 'hosts.csv', + 'procs.csv', + 'addrs.csv', + 'ports.csv', + 'users.csv' + ) + + self.iconStart = QtGui.QIcon().fromTheme("media-playback-start") + self.iconPause = QtGui.QIcon().fromTheme("media-playback-pause") + + if QtGui.QIcon.hasThemeIcon("document-new") == False: + self._configure_buttons_icons() + + #Sometimes a maximized window which had been minimized earlier won't unminimize + #To workaround, we explicitely maximize such windows when unminimizing happens + def changeEvent(self, event): + if event.type() == QtCore.QEvent.WindowStateChange: + if event.oldState() & QtCore.Qt.WindowMinimized and event.oldState() & QtCore.Qt.WindowMaximized: + #a previously minimized maximized window ... + if self.windowState() ^ QtCore.Qt.WindowMinimized and self._current_desktop == "KDE": + # is not minimized anymore, i.e. it was unminimized + # docs: https://doc.qt.io/qt-5/qwidget.html#setWindowState + self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) + + def showEvent(self, event): + super(StatsDialog, self).showEvent(event) + self._shown_trigger.emit() + window_title = QC.translate("stats", "OpenSnitch Network Statistics {0}").format(version) + if self._address is not None: + window_title = QC.translate("stats", "OpenSnitch Network Statistics for {0}").format(self._address) + self.nodeLabel.setText(self._address) + self._load_settings() + self._add_rulesTree_nodes() + self.setWindowTitle(window_title) + self._refresh_active_table() + + def eventFilter(self, source, event): + if event.type() == QtCore.QEvent.KeyPress: + if event.matches(QtGui.QKeySequence.Copy): + self._copy_selected_rows() + return True + elif event.key() == QtCore.Qt.Key_Delete: + table = self._get_active_table() + selection = table.selectionModel().selectedRows() + if selection: + model = table.model() + self._table_menu_delete(2, model, selection) + # we need to manually refresh the model + table.selectionModel().clear() + self._refresh_active_table() + return True + return super(StatsDialog, self).eventFilter(source, event) + + def _configure_buttons_icons(self): + self.iconStart = self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_MediaPlay")) + self.iconPause = self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_MediaPause")) + + self.newRuleButton.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_FileIcon"))) + self.delRuleButton.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_TrashIcon"))) + self.editRuleButton.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_FileDialogDetailedView"))) + self.saveButton.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_DialogSaveButton"))) + self.prefsButton.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_FileDialogDetailedView"))) + self.startButton.setIcon(self.iconStart) + self.cmdProcDetails.setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_FileDialogContentsView"))) + self.TABLES[self.TAB_MAIN]['cmdCleanStats'].setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_DialogResetButton"))) + for idx in range(1,8): + self.TABLES[idx]['cmd'].setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_ArrowLeft"))) + if self.TABLES[idx]['cmdCleanStats'] != None: + self.TABLES[idx]['cmdCleanStats'].setIcon(self.style().standardIcon(getattr(QtWidgets.QStyle, "SP_DialogResetButton"))) + + def _load_settings(self): + dialog_geometry = self._cfg.getSettings(Config.STATS_GEOMETRY) + dialog_last_tab = self._cfg.getSettings(Config.STATS_LAST_TAB) + dialog_general_filter_text = self._cfg.getSettings(Config.STATS_FILTER_TEXT) + dialog_general_filter_action = self._cfg.getSettings(Config.STATS_FILTER_ACTION) + dialog_general_limit_results = self._cfg.getSettings(Config.STATS_LIMIT_RESULTS) + if dialog_geometry != None: + self.restoreGeometry(dialog_geometry) + if dialog_last_tab != None: + self.tabWidget.setCurrentIndex(int(dialog_last_tab)) + if dialog_general_filter_text != None: + # prevent from firing textChanged signal + self.filterLine.blockSignals(True); + self.filterLine.setText(dialog_general_filter_text) + self.filterLine.blockSignals(False); + if dialog_general_filter_action != None: + self.comboAction.setCurrentIndex(int(dialog_general_filter_action)) + if dialog_general_limit_results != None: + # XXX: a little hack, because if the saved index is 0, the signal is not fired. + # XXX: this causes to fire the event twice + self.limitCombo.blockSignals(True); + self.limitCombo.setCurrentIndex(4) + self.limitCombo.setCurrentIndex(int(dialog_general_limit_results)) + self.limitCombo.blockSignals(False); + + rules_splitter_pos = self._cfg.getSettings(Config.STATS_RULES_SPLITTER_POS) + if type(rules_splitter_pos) == QtCore.QByteArray: + self.rulesSplitter.restoreState(rules_splitter_pos) + rulesSizes = self.rulesSplitter.sizes() + if self.IN_DETAIL_VIEW[self.TAB_RULES] == True: + self.comboRulesFilter.setVisible(False) + elif len(rulesSizes) > 0: + self.comboRulesFilter.setVisible(rulesSizes[0] == 0) + else: + w = self.rulesSplitter.width() + self.rulesSplitter.setSizes([int(w/4), int(w/2)]) + + self._restore_details_view_columns(self.eventsTable.horizontalHeader(), Config.STATS_GENERAL_COL_STATE) + self._restore_details_view_columns(self.nodesTable.horizontalHeader(), Config.STATS_NODES_COL_STATE) + self._restore_details_view_columns(self.rulesTable.horizontalHeader(), Config.STATS_RULES_COL_STATE) + + rulesTreeNodes_expanded = self._cfg.getBool(Config.STATS_RULES_TREE_EXPANDED_1) + if rulesTreeNodes_expanded != None: + rules_tree_nodes = self._get_rulesTree_item(self.RULES_TREE_NODES) + if rules_tree_nodes != None: + rules_tree_nodes.setExpanded(rulesTreeNodes_expanded) + rulesTreeApps_expanded = self._cfg.getBool(Config.STATS_RULES_TREE_EXPANDED_0) + if rulesTreeApps_expanded != None: + rules_tree_apps = self._get_rulesTree_item(self.RULES_TREE_APPS) + if rules_tree_apps != None: + rules_tree_apps.setExpanded(rulesTreeApps_expanded) + + + def _save_settings(self): + self._cfg.setSettings(Config.STATS_GEOMETRY, self.saveGeometry()) + self._cfg.setSettings(Config.STATS_LAST_TAB, self.tabWidget.currentIndex()) + self._cfg.setSettings(Config.STATS_LIMIT_RESULTS, self.limitCombo.currentIndex()) + self._cfg.setSettings(Config.STATS_FILTER_TEXT, self.filterLine.text()) + + header = self.eventsTable.horizontalHeader() + self._cfg.setSettings(Config.STATS_GENERAL_COL_STATE, header.saveState()) + nodesHeader = self.nodesTable.horizontalHeader() + self._cfg.setSettings(Config.STATS_NODES_COL_STATE, nodesHeader.saveState()) + rulesHeader = self.rulesTable.horizontalHeader() + self._cfg.setSettings(Config.STATS_RULES_COL_STATE, rulesHeader.saveState()) + + rules_tree_apps = self._get_rulesTree_item(self.RULES_TREE_APPS) + if rules_tree_apps != None: + self._cfg.setSettings(Config.STATS_RULES_TREE_EXPANDED_0, rules_tree_apps.isExpanded()) + rules_tree_nodes = self._get_rulesTree_item(self.RULES_TREE_NODES) + if rules_tree_nodes != None: + self._cfg.setSettings(Config.STATS_RULES_TREE_EXPANDED_1, rules_tree_nodes.isExpanded()) + + + def _del_rule(self, rule_name, node_addr): + nid, noti = self._nodes.delete_rule(rule_name, node_addr, self._notification_callback) + self._notifications_sent[nid] = noti + + # https://stackoverflow.com/questions/40225270/copy-paste-multiple-items-from-qtableview-in-pyqt4 + def _copy_selected_rows(self): + cur_idx = self.tabWidget.currentIndex() + selection = self.TABLES[cur_idx]['view'].selectedIndexes() + if selection: + rows = sorted(index.row() for index in selection) + columns = sorted(index.column() for index in selection) + rowcount = rows[-1] - rows[0] + 1 + colcount = columns[-1] - columns[0] + 1 + table = [[''] * colcount for _ in range(rowcount)] + for index in selection: + row = index.row() - rows[0] + column = index.column() - columns[0] + table[row][column] = index.data() + stream = io.StringIO() + csv.writer(stream, delimiter=',').writerows(table) + QtWidgets.qApp.clipboard().setText(stream.getvalue()) + + + def _configure_rules_contextual_menu(self, pos): + try: + cur_idx = self.tabWidget.currentIndex() + table = self._get_active_table() + model = table.model() + + selection = table.selectionModel().selectedRows() + if not selection: + return + + menu = QtWidgets.QMenu() + durMenu = QtWidgets.QMenu(self.COL_STR_DURATION) + actionMenu = QtWidgets.QMenu(self.COL_STR_ACTION) + nodesMenu = QtWidgets.QMenu(QC.translate("stats", "Apply to")) + + nodes_menu = [] + if self._nodes.count() > 0: + for node in self._nodes.get_nodes(): + nodes_menu.append([nodesMenu.addAction(node), node]) + menu.addMenu(nodesMenu) + + _actAllow = actionMenu.addAction(QC.translate("stats", "Allow")) + _actDeny = actionMenu.addAction(QC.translate("stats", "Deny")) + _actReject = actionMenu.addAction(QC.translate("stats", "Reject")) + menu.addMenu(actionMenu) + + _durAlways = durMenu.addAction(QC.translate("stats", "Always")) + _durUntilReboot = durMenu.addAction(QC.translate("stats", "Until reboot")) + _dur1h = durMenu.addAction(Config.DURATION_1h) + _dur30m = durMenu.addAction(Config.DURATION_30m) + _dur15m = durMenu.addAction(Config.DURATION_15m) + _dur5m = durMenu.addAction(Config.DURATION_5m) + menu.addMenu(durMenu) + + is_rule_enabled = model.index(selection[0].row(), self.COL_R_ENABLED).data() + menu_label_enable = QC.translate("stats", "Disable") + if is_rule_enabled == "False": + menu_label_enable = QC.translate("stats", "Enable") + + _menu_enable = menu.addAction(QC.translate("stats", menu_label_enable)) + _menu_duplicate = menu.addAction(QC.translate("stats", "Duplicate")) + _menu_edit = menu.addAction(QC.translate("stats", "Edit")) + _menu_delete = menu.addAction(QC.translate("stats", "Delete")) + + # move away menu a few pixels to the right, to avoid clicking on it by mistake + point = QtCore.QPoint(pos.x()+10, pos.y()+5) + action = menu.exec_(table.mapToGlobal(point)) + + model = table.model() + + if self._nodes.count() > 0: + for nmenu in nodes_menu: + node_action = nmenu[0] + node_addr = nmenu[1] + if action == node_action: + ret = Message.yes_no( + QC.translate("stats", " Apply this rule to {0} ".format(node_addr)), + QC.translate("stats", " Are you sure?"), + QtWidgets.QMessageBox.Warning) + if ret == QtWidgets.QMessageBox.Cancel: + return False + self._table_menu_apply_to_node(cur_idx, model, selection, node_addr) + return + + if action == _menu_delete: + self._table_menu_delete(cur_idx, model, selection) + elif action == _menu_edit: + self._table_menu_edit(cur_idx, model, selection) + elif action == _menu_enable: + self._table_menu_enable(cur_idx, model, selection, is_rule_enabled) + elif action == _menu_duplicate: + self._table_menu_duplicate(cur_idx, model, selection) + elif action == _durAlways: + self._table_menu_change_rule_field(cur_idx, model, selection, "duration", Config.DURATION_ALWAYS) + elif action == _dur1h: + self._table_menu_change_rule_field(cur_idx, model, selection, "duration", Config.DURATION_1h) + elif action == _dur30m: + self._table_menu_change_rule_field(cur_idx, model, selection, "duration", Config.DURATION_30m) + elif action == _dur15m: + self._table_menu_change_rule_field(cur_idx, model, selection, "duration", Config.DURATION_15m) + elif action == _dur5m: + self._table_menu_change_rule_field(cur_idx, model, selection, "duration", Config.DURATION_5m) + elif action == _durUntilReboot: + self._table_menu_change_rule_field(cur_idx, model, selection, "duration", Config.DURATION_UNTIL_RESTART) + elif action == _actAllow: + self._table_menu_change_rule_field(cur_idx, model, selection, "action", Config.ACTION_ALLOW) + elif action == _actDeny: + self._table_menu_change_rule_field(cur_idx, model, selection, "action", Config.ACTION_DENY) + elif action == _actReject: + self._table_menu_change_rule_field(cur_idx, model, selection, "action", Config.ACTION_REJECT) + + except Exception as e: + print(e) + finally: + self._clear_rows_selection() + return True + + def _table_menu_duplicate(self, cur_idx, model, selection): + + for idx in selection: + rule_name = model.index(idx.row(), self.COL_R_NAME).data() + node_addr = model.index(idx.row(), self.COL_R_NODE).data() + + records = None + for idx in range(0,100): + records = self._get_rule(rule_name, node_addr) + if records == None or records.size() == -1: + rule = self._rules_dialog.get_rule_from_records(records) + rule.name = "cloned-{0}-{1}".format(idx, rule.name) + self._db.insert_rule(rule, node_addr) + break + + if records != None and records.size() == -1: + noti = ui_pb2.Notification(type=ui_pb2.CHANGE_RULE, rules=[rule]) + nid = self._nodes.send_notification(node_addr, noti, self._notification_callback) + if nid != None: + self._notifications_sent[nid] = noti + + def _table_menu_apply_to_node(self, cur_idx, model, selection, node_addr): + + for idx in selection: + rule_name = model.index(idx.row(), self.COL_R_NAME).data() + records = self._get_rule(rule_name, None) + rule = self._rules_dialog.get_rule_from_records(records) + + noti = ui_pb2.Notification(type=ui_pb2.CHANGE_RULE, rules=[rule]) + nid = self._nodes.send_notification(node_addr, noti, self._notification_callback) + if nid != None: + self._db.insert_rule(rule, node_addr) + self._notifications_sent[nid] = noti + + def _table_menu_change_rule_field(self, cur_idx, model, selection, field, value): + for idx in selection: + rule_name = model.index(idx.row(), self.COL_R_NAME).data() + node_addr = model.index(idx.row(), self.COL_R_NODE).data() + + records = self._get_rule(rule_name, node_addr) + rule = self._rules_dialog.get_rule_from_records(records) + + self._db.update(table="rules", fields="{0}=?".format(field), + values=[value], condition="name='{0}' AND node='{1}'".format(rule_name, node_addr), + action_on_conflict="") + + if field == "action": + rule.action = value + elif field == "duration": + rule.duration = value + elif field == "precedence": + rule.precedence = value + + noti = ui_pb2.Notification(type=ui_pb2.CHANGE_RULE, rules=[rule]) + nid = self._nodes.send_notification(node_addr, noti, self._notification_callback) + if nid != None: + self._notifications_sent[nid] = noti + + def _table_menu_enable(self, cur_idx, model, selection, is_rule_enabled): + rule_status = "False" if is_rule_enabled == "True" else "True" + + for idx in selection: + rule_name = model.index(idx.row(), self.COL_R_NAME).data() + node_addr = model.index(idx.row(), self.COL_R_NODE).data() + + records = self._get_rule(rule_name, node_addr) + rule = self._rules_dialog.get_rule_from_records(records) + rule_type = ui_pb2.DISABLE_RULE if is_rule_enabled == "True" else ui_pb2.ENABLE_RULE + + self._db.update(table="rules", fields="enabled=?", + values=[rule_status], condition="name='{0}' AND node='{1}'".format(rule_name, node_addr), + action_on_conflict="") + + noti = ui_pb2.Notification(type=rule_type, rules=[rule]) + nid = self._nodes.send_notification(node_addr, noti, self._notification_callback) + if nid != None: + self._notifications_sent[nid] = noti + + def _table_menu_delete(self, cur_idx, model, selection): + ret = Message.yes_no( + QC.translate("stats", " Your are about to delete this rule. "), + QC.translate("stats", " Are you sure?"), + QtWidgets.QMessageBox.Warning) + if ret == QtWidgets.QMessageBox.Cancel: + return False + + for idx in selection: + name = model.index(idx.row(), self.COL_R_NAME).data() + node = model.index(idx.row(), self.COL_R_NODE).data() + self._del_rule(name, node) + + def _table_menu_edit(self, cur_idx, model, selection): + + for idx in selection: + name = model.index(idx.row(), self.COL_R_NAME).data() + node = model.index(idx.row(), self.COL_R_NODE).data() + records = self._get_rule(name, node) + if records == None or records == -1: + Message.ok("Rule error", + QC.translate("stats", "Rule not found by that name and node"), + QtWidgets.QMessageBox.Warning) + return + self._rules_dialog.edit_rule(records, node) + break + + # ignore updates while the user is using the scrollbar. + def _cb_scrollbar_pressed(self): + self.scrollbar_active = True + + def _cb_scrollbar_released(self): + self.scrollbar_active = False + + def _cb_proc_details_clicked(self): + table = self._tables[self.tabWidget.currentIndex()] + nrows = table.model().rowCount() + pids = {} + for row in range(0, nrows): + pid = table.model().index(row, self.COL_PID).data() + node = table.model().index(row, self.COL_NODE).data() + if pid not in pids: + pids[pid] = node + + self._proc_details_dialog.monitor(pids) + + @QtCore.pyqtSlot(ui_pb2.NotificationReply) + def _cb_notification_callback(self, reply): + if reply.id in self._notifications_sent: + if reply.code == ui_pb2.ERROR: + Message.ok( + QC.translate("stats", + "<b>Error:</b><br><br>", + "{0}").format(reply.data), + QtWidgets.QMessageBox.Warning) + + else: + Message.ok( + QC.translate("stats", "Warning:"), + "{0}".format(reply.data), + QtWidgets.QMessageBox.Warning) + + def _cb_tab_changed(self, index): + self.comboAction.setVisible(index == self.TAB_MAIN) + + self.TABLES[index]['cmdCleanStats'].setVisible(True) + if index == self.TAB_MAIN: + self._set_events_query() + else: + if index == self.TAB_RULES: + # display the clean buton only if not in detail view + self.TABLES[index]['cmdCleanStats'].setVisible( self.IN_DETAIL_VIEW[index] ) + self._add_rulesTree_nodes() + + elif index == self.TAB_PROCS: + # make the button visible depending if we're in the detail view + nrows = self._get_active_table().model().rowCount() + self.cmdProcDetails.setVisible(self.IN_DETAIL_VIEW[index] and nrows > 0) + elif index == self.TAB_NODES: + self.TABLES[index]['cmdCleanStats'].setVisible( self.IN_DETAIL_VIEW[index] ) + + self._refresh_active_table() + + def _cb_table_context_menu(self, pos): + cur_idx = self.tabWidget.currentIndex() + if cur_idx != self.TAB_RULES or self.IN_DETAIL_VIEW[self.TAB_RULES] == True: + # the only table with context menu for now is the main rules table + return + + self._context_menu_active = True + refresh_table = self._configure_rules_contextual_menu(pos) + self._context_menu_active = False + if refresh_table: + self._refresh_active_table() + + + def _cb_table_header_clicked(self, pos, sortIdx): + cur_idx = self.tabWidget.currentIndex() + # TODO: allow ordering by Network column + if cur_idx == self.TAB_ADDRS and pos == 2: + return + + model = self._get_active_table().model() + qstr = model.query().lastQuery().split("ORDER BY")[0] + + q = qstr.strip(" ") + " ORDER BY %d %s" % (pos+1, self.SORT_ORDER[sortIdx]) + if cur_idx > 0 and self.TABLES[cur_idx]['cmd'].isVisible() == False: + self.TABLES[cur_idx]['last_order_by'] = pos+1 + self.TABLES[cur_idx]['last_order_to'] = sortIdx + + q = qstr.strip(" ") + self._get_order() + + q += self._get_limit() + self.setQuery(model, q) + + def _cb_events_filter_line_changed(self, text): + cur_idx = self.tabWidget.currentIndex() + + model = self.TABLES[cur_idx]['view'].model() + qstr = None + if cur_idx == StatsDialog.TAB_MAIN: + self._cfg.setSettings(Config.STATS_FILTER_TEXT, text) + self._set_events_query() + return + elif cur_idx == StatsDialog.TAB_NODES: + qstr = self._get_nodes_filter_query(model.query().lastQuery(), text) + elif self.IN_DETAIL_VIEW[cur_idx] == True: + qstr = self._get_indetail_filter_query(model.query().lastQuery(), text) + else: + where_clause = self._get_filter_line_clause(cur_idx, text) + qstr = self._db.get_query( self.TABLES[cur_idx]['name'], self.TABLES[cur_idx]['display_fields'] ) + \ + where_clause + self._get_order() + if text == "": + qstr = qstr + self._get_limit() + + if qstr != None: + self.setQuery(model, qstr) + + def _cb_limit_combo_changed(self, idx): + if self.tabWidget.currentIndex() == self.TAB_MAIN: + self._set_events_query() + else: + model = self._get_active_table().model() + qstr = model.query().lastQuery() + if "LIMIT" in qstr: + qs = qstr.split(" LIMIT ") + q = qs[0] + l = qs[1] + qstr = q + self._get_limit() + else: + qstr = qstr + self._get_limit() + self.setQuery(model, qstr) + + def _cb_combo_action_changed(self, idx): + if self.tabWidget.currentIndex() != self.TAB_MAIN: + return + + self._cfg.setSettings(Config.STATS_GENERAL_FILTER_ACTION, idx) + self._set_events_query() + + def _cb_clean_sql_clicked(self, idx): + cur_idx = self.tabWidget.currentIndex() + if self.tabWidget.currentIndex() == StatsDialog.TAB_RULES: + self._db.empty_rule(self.TABLES[cur_idx]['label'].text()) + elif self.IN_DETAIL_VIEW[cur_idx]: + model = self._get_active_table().model() + # get left side of the query: * GROUP BY ... + qstr = model.query().lastQuery().split("GROUP BY")[0] + # get right side of the query: ... WHERE * + q = qstr.split("WHERE") + + table = self.TABLES[cur_idx]['name'] + label = self.TABLES[cur_idx]['label'].text() + + field = "dst_host" + if cur_idx == self.TAB_NODES: + field = "node" + if label[0] == '/': + label = "unix:{0}".format(label) + elif cur_idx == self.TAB_PROCS: + field = "process" + elif cur_idx == self.TAB_ADDRS: + field = "dst_ip" + elif cur_idx == self.TAB_PORTS: + field = "dst_port" + elif cur_idx == self.TAB_USERS: + field = "uid" + + self._db.remove("DELETE FROM {0} WHERE what = '{1}'".format(table, label)) + self._db.remove("DELETE FROM connections WHERE {0} = '{1}'".format(field, label)) + else: + self._db.clean(self.TABLES[cur_idx]['name']) + self._refresh_active_table() + + def _cb_cmd_back_clicked(self, idx): + try: + cur_idx = self.tabWidget.currentIndex() + self._clear_rows_selection() + self.IN_DETAIL_VIEW[cur_idx] = False + + self._set_active_widgets(False) + if cur_idx == StatsDialog.TAB_RULES: + self._restore_rules_tab_widgets(True) + return + elif cur_idx == StatsDialog.TAB_PROCS: + self.cmdProcDetails.setVisible(False) + + model = self._get_active_table().model() + where_clause = "" + if self.TABLES[cur_idx]['filterLine'] != None: + filter_text = self.TABLES[cur_idx]['filterLine'].text() + where_clause = self._get_filter_line_clause(cur_idx, filter_text) + + self.setQuery(model, + self._db.get_query( + self.TABLES[cur_idx]['name'], + self.TABLES[cur_idx]['display_fields']) + where_clause + " " + self._get_order() + self._get_limit() + ) + finally: + self._restore_details_view_columns( + self.TABLES[cur_idx]['view'].horizontalHeader(), + "{0}{1}".format(Config.STATS_VIEW_COL_STATE, cur_idx) + ) + self._restore_scroll_value() + self._restore_last_selected_row() + + def _cb_main_table_double_clicked(self, row): + data = row.data() + idx = row.column() + cur_idx = 1 + + if idx == StatsDialog.COL_NODE: + cur_idx = self.TAB_NODES + self.IN_DETAIL_VIEW[cur_idx] = True + self.LAST_SELECTED_ITEM = row.model().index(row.row(), self.COL_NODE).data() + self.tabWidget.setCurrentIndex(cur_idx) + self._set_active_widgets(True, str(data)) + p, addr = self._nodes.get_addr(data) + self._set_nodes_query(addr) + + elif idx == StatsDialog.COL_PROCS: + cur_idx = self.TAB_PROCS + self.IN_DETAIL_VIEW[cur_idx] = True + self.LAST_SELECTED_ITEM = row.model().index(row.row(), self.COL_PROCS).data() + self.tabWidget.setCurrentIndex(cur_idx) + self._set_active_widgets(True, str(data)) + self._set_process_query(data) + + elif idx == StatsDialog.COL_RULES: + cur_idx = self.TAB_RULES + self.IN_DETAIL_VIEW[cur_idx] = True + self.LAST_SELECTED_ITEM = row.model().index(row.row(), self.COL_RULES).data() + r_name, node = self._set_rules_tab_active(row, cur_idx, self.COL_RULES, self.COL_NODE) + self._set_active_widgets(True, str(data)) + self._set_rules_query(r_name, node) + + else: + return + + self._restore_details_view_columns( + self.TABLES[cur_idx]['view'].horizontalHeader(), + "{0}{1}".format(Config.STATS_VIEW_DETAILS_COL_STATE, cur_idx) + ) + + def _cb_table_double_clicked(self, row): + cur_idx = self.tabWidget.currentIndex() + if self.IN_DETAIL_VIEW[cur_idx]: + return + self.IN_DETAIL_VIEW[cur_idx] = True + self.LAST_SELECTED_ITEM = row.model().index(row.row(), self.COL_TIME).data() + self.LAST_SCROLL_VALUE = self.TABLES[cur_idx]['view'].vScrollBar.value() + + data = row.data() + + if cur_idx == self.TAB_RULES: + rule_name = row.model().index(row.row(), self.COL_R_NAME).data() + self._set_active_widgets(True, rule_name) + r_name, node = self._set_rules_tab_active(row, cur_idx, self.COL_R_NAME, self.COL_R_NODE) + self.LAST_SELECTED_ITEM = row.model().index(row.row(), self.COL_R_NAME).data() + self._set_rules_query(r_name, node) + self._restore_details_view_columns( + self.TABLES[cur_idx]['view'].horizontalHeader(), + "{0}{1}".format(Config.STATS_VIEW_DETAILS_COL_STATE, cur_idx) + ) + return + if cur_idx == self.TAB_NODES: + data = row.model().index(row.row(), self.COL_NODE).data() + self.LAST_SELECTED_ITEM = row.model().index(row.row(), self.COL_NODE).data() + if cur_idx > self.TAB_RULES: + self.LAST_SELECTED_ITEM = row.model().index(row.row(), self.COL_WHAT).data() + data = row.model().index(row.row(), self.COL_WHAT).data() + + + self._set_active_widgets(True, str(data)) + + if cur_idx == StatsDialog.TAB_NODES: + self._set_nodes_query(data) + elif cur_idx == StatsDialog.TAB_HOSTS: + self._set_hosts_query(data) + elif cur_idx == StatsDialog.TAB_PROCS: + self._set_process_query(data) + elif cur_idx == StatsDialog.TAB_ADDRS: + lbl_text = self.TABLES[cur_idx]['label'].text() + if lbl_text != "": + asn = self.asndb.get_asn(lbl_text) + if asn != "": + lbl_text += " (" + asn + ")" + self.TABLES[cur_idx]['label'].setText(lbl_text) + self._set_addrs_query(data) + elif cur_idx == StatsDialog.TAB_PORTS: + self._set_ports_query(data) + elif cur_idx == StatsDialog.TAB_USERS: + self._set_users_query(data) + + self._restore_details_view_columns( + self.TABLES[cur_idx]['view'].horizontalHeader(), + "{0}{1}".format(Config.STATS_VIEW_DETAILS_COL_STATE, cur_idx) + ) + + # selection changes occur before tableview's clicked event + # if there're no rows selected, accept the selection. Otherwise clean it. + def _cb_table_selection_changed(self, selected, deselected): + cur_idx = self.tabWidget.currentIndex() + + # only update the flag (that updates data), if there's more than 1 + # row selected. When using the keyboard to move around, 1 row will + # be selected to indicate where you are. + + # NOTE: in some qt versions you can select a row and setQuery() won't + # reset the selection, but in others it gets resetted. + self.TABLES[cur_idx]['rows_selected'] = len(self.TABLES[cur_idx]['view'].selectionModel().selectedRows(0)) > 1 + + def _cb_prefs_clicked(self): + self._prefs_dialog.show() + + def _cb_rules_filter_combo_changed(self, idx): + if idx == self.RULES_TREE_APPS: + self._set_rules_filter() + elif idx == self.RULES_COMBO_PERMANENT: + self._set_rules_filter(self.RULES_TREE_APPS, self.RULES_TREE_PERMANENT) + elif idx == self.RULES_COMBO_TEMPORARY: + self._set_rules_filter(self.RULES_TREE_APPS, self.RULES_TREE_TEMPORARY) + + def _cb_rules_tree_item_clicked(self, item, col): + """ + Event fired when the user clicks on the left panel of the rules tab + """ + item_model = self.rulesTreePanel.indexFromItem(item, col) + parent = item.parent() + parent_row = -1 + if parent != None: + parent_model = self.rulesTreePanel.indexFromItem(parent, col) + parent_row = parent_model.row() + + self._set_rules_filter(parent_row, item_model.row(), item.text(0)) + + def _cb_rules_splitter_moved(self, pos, index): + self.comboRulesFilter.setVisible(pos == 0) + self._cfg.setSettings(Config.STATS_RULES_SPLITTER_POS, self.rulesSplitter.saveState()) + + def _cb_start_clicked(self): + if self.daemon_connected == False: + self.startButton.setChecked(False) + self.startButton.setIcon(self.iconStart) + return + + self.update_interception_status(self.startButton.isChecked()) + self._status_changed_trigger.emit(self.startButton.isChecked()) + + if self.startButton.isChecked(): + nid, noti = self._nodes.start_interception(_callback=self._notification_callback) + else: + nid, noti = self._nodes.stop_interception(_callback=self._notification_callback) + + self._notifications_sent[nid] = noti + + def _cb_new_rule_clicked(self): + self._rules_dialog.new_rule() + + def _cb_edit_rule_clicked(self): + cur_idx = self.tabWidget.currentIndex() + records = self._get_rule(self.TABLES[cur_idx]['label'].text(), self.nodeRuleLabel.text()) + if records == None: + return + + self._rules_dialog.edit_rule(records, self.nodeRuleLabel.text()) + + def _cb_del_rule_clicked(self): + ret = Message.yes_no( + QC.translate("stats", " You are about to delete this rule. "), + QC.translate("stats", " Are you sure?"), + QtWidgets.QMessageBox.Warning) + if ret == QtWidgets.QMessageBox.Cancel: + return + + self._del_rule(self.TABLES[self.tabWidget.currentIndex()]['label'].text(), self.nodeRuleLabel.text()) + self.TABLES[self.TAB_RULES]['cmd'].click() + self.nodeRuleLabel.setText("") + self._refresh_active_table() + + def _cb_enable_rule_toggled(self, state): + rule = ui_pb2.Rule(name=self.TABLES[self.tabWidget.currentIndex()]['label'].text()) + rule.enabled = False + rule.action = "" + rule.duration = "" + rule.operator.type = "" + rule.operator.operand = "" + rule.operator.data = "" + + notType = ui_pb2.DISABLE_RULE + if state == True: + notType = ui_pb2.ENABLE_RULE + rule.enabled = state + noti = ui_pb2.Notification(type=notType, rules=[rule]) + self._notification_trigger.emit(noti) + + def _cb_prev_button_clicked(self): + model = self._get_active_table().model() + model.fetchMore() + + def _cb_next_button_clicked(self): + model = self._get_active_table().model() + model.fetchMore() + + def _cb_help_button_clicked(self): + QuickHelp.show( + QC.translate("stats", + "<p><b>Quick help</b></p>" \ + "<p>- Use CTRL+c to copy selected rows.</p>" \ + "<p>- Use Home,End,PgUp,PgDown,PgUp,Up or Down keys to navigate rows.</p>" \ + "<p>- Use right click on a row to stop refreshing the view.</p>" \ + "<p>- Selecting more than one row also stops refreshing the view.</p>" + "<p>- On the Events view, clicking on columns Node, Process or Rule<br>" \ + "jumps to the view of the selected item.</p>" \ + "<p>- On the rest of the views, double click on a row to get detailed<br>" \ + " information.</p><br>" \ + "<p>For more information visit the <a href=\"{0}\">wiki</a></p>" \ + "<br>".format(Config.HELP_URL) + ) + ) + + # must be called after setModel() or setQuery() + def _show_columns(self): + cols = self._cfg.getSettings(Config.STATS_SHOW_COLUMNS) + if cols == None: + return + + for c in range(StatsDialog.GENERAL_COL_NUM): + self.eventsTable.setColumnHidden(c, str(c) not in cols) + + def _update_status_label(self, running=False, text=FIREWALL_DISABLED): + self.statusLabel.setText("%12s" % text) + if running: + self.statusLabel.setStyleSheet('color: green; margin: 5px') + self.startButton.setIcon(self.iconPause) + else: + self.statusLabel.setStyleSheet('color: rgb(206, 92, 0); margin: 5px') + self.startButton.setIcon(self.iconStart) + + def _get_rulesTree_item(self, index): + try: + return self.rulesTreePanel.topLevelItem(index) + except Exception: + return None + + def _add_rulesTree_nodes(self): + if self._nodes.count() > 0: + nodesItem = self.rulesTreePanel.topLevelItem(self.RULES_TREE_NODES) + nodesItem.takeChildren() + for n in self._nodes.get_nodes(): + nodesItem.addChild(QtWidgets.QTreeWidgetItem([n])) + + def _clear_rows_selection(self): + cur_idx = self.tabWidget.currentIndex() + self.TABLES[cur_idx]['view'].selectionModel().reset() + self.TABLES[cur_idx]['rows_selected'] = False + + def _are_rows_selected(self): + cur_idx = self.tabWidget.currentIndex() + return self.TABLES[cur_idx]['rows_selected'] + + def _get_rule(self, rule_name, node_name): + """ + get rule records, given the name of the rule and the node + """ + cur_idx = self.tabWidget.currentIndex() + records = self._db.get_rule(rule_name, node_name) + if records.next() == False: + print("[stats dialog] edit rule, no records: ", rule_name, node_name) + self.TABLES[cur_idx]['cmd'].click() + return None + + return records + + def _get_filter_line_clause(self, idx, text): + if text == "": + return "" + + + if idx == StatsDialog.TAB_RULES: + return " WHERE rules.name LIKE '%{0}%' ".format(text) + elif idx == StatsDialog.TAB_HOSTS or idx == StatsDialog.TAB_PROCS or \ + idx == StatsDialog.TAB_ADDRS or idx == StatsDialog.TAB_PORTS: + return " WHERE what LIKE '%{0}%' ".format(text) + + return "" + + def _get_limit(self): + return " " + self.LIMITS[self.limitCombo.currentIndex()] + + def _get_order(self, field=None): + cur_idx = self.tabWidget.currentIndex() + order_field = self.TABLES[cur_idx]['last_order_by'] + if field != None: + order_field = field + return " ORDER BY %s %s" % (order_field, self.SORT_ORDER[self.TABLES[cur_idx]['last_order_to']]) + + def _refresh_active_table(self): + model = self._get_active_table().model() + lastQuery = model.query().lastQuery() + if "LIMIT" not in lastQuery: + lastQuery += self._get_limit() + self.setQuery(model, lastQuery) + + def _get_active_table(self): + return self.TABLES[self.tabWidget.currentIndex()]['view'] + + def _set_active_widgets(self, state, label_txt=""): + cur_idx = self.tabWidget.currentIndex() + self._clear_rows_selection() + self.TABLES[cur_idx]['label'].setVisible(state) + self.TABLES[cur_idx]['label'].setText(label_txt) + self.TABLES[cur_idx]['cmd'].setVisible(state) + + if self.TABLES[cur_idx]['filterLine'] != None: + self.TABLES[cur_idx]['filterLine'].setVisible(not state) + + if self.TABLES[cur_idx].get('cmdCleanStats') != None: + if cur_idx == StatsDialog.TAB_RULES or cur_idx == StatsDialog.TAB_NODES: + self.TABLES[cur_idx]['cmdCleanStats'].setVisible(state) + + header = self.TABLES[cur_idx]['view'].horizontalHeader() + if state == True: + # going to normal state + self._cfg.setSettings("{0}{1}".format(Config.STATS_VIEW_COL_STATE, cur_idx), header.saveState()) + else: + # going to details state + self._cfg.setSettings("{0}{1}".format(Config.STATS_VIEW_DETAILS_COL_STATE, cur_idx), header.saveState()) + + def _restore_last_selected_row(self): + cur_idx = self.tabWidget.currentIndex() + col = self.COL_TIME + if cur_idx == self.TAB_RULES: + col = self.TAB_RULES + elif cur_idx == self.TAB_NODES: + col = self.TAB_RULES + + self.TABLES[cur_idx]['view'].selectItem(self.LAST_SELECTED_ITEM, col) + self.LAST_SELECTED_ITEM = "" + + def _restore_scroll_value(self): + if self.LAST_SCROLL_VALUE != None: + cur_idx = self.tabWidget.currentIndex() + self.TABLES[cur_idx]['view'].vScrollBar.setValue(self.LAST_SCROLL_VALUE) + self.LAST_SCROLL_VALUE = None + + def _restore_details_view_columns(self, header, settings_key): + header.blockSignals(True); + + col_state = self._cfg.getSettings(settings_key) + if type(col_state) == QtCore.QByteArray: + header.restoreState(col_state) + + header.blockSignals(False); + + def _restore_rules_tab_widgets(self, active): + self.delRuleButton.setVisible(not active) + self.editRuleButton.setVisible(not active) + self.nodeRuleLabel.setText("") + self.rulesTreePanel.setVisible(active) + + if active: + self.rulesSplitter.refresh() + self.comboRulesFilter.setVisible(self.rulesTreePanel.width() == 0) + + items = self.rulesTreePanel.selectedItems() + if len(items) == 0: + self._set_rules_filter() + return + + item_m = self.rulesTreePanel.indexFromItem(items[0], 0) + parent = item_m.parent() + if parent != None: + self._set_rules_filter(parent.row(), item_m.row(), item_m.data()) + + def _set_rules_tab_active(self, row, cur_idx, name_idx, node_idx): + data = row.data() + self._restore_rules_tab_widgets(False) + + self.comboRulesFilter.setVisible(False) + + r_name = row.model().index(row.row(), name_idx).data() + node = row.model().index(row.row(), node_idx).data() + self.nodeRuleLabel.setText(node) + self.tabWidget.setCurrentIndex(cur_idx) + + return r_name, node + + def _set_events_query(self): + if self.tabWidget.currentIndex() != self.TAB_MAIN: + return + + model = self.TABLES[self.TAB_MAIN]['view'].model() + qstr = self._db.get_query(self.TABLES[self.TAB_MAIN]['name'], self.TABLES[self.TAB_MAIN]['display_fields']) + + filter_text = self.filterLine.text() + action = "" + if self.comboAction.currentIndex() == 1: + action = "Action = \"{0}\"".format(Config.ACTION_ALLOW) + elif self.comboAction.currentIndex() == 2: + action = "Action = \"{0}\"".format(Config.ACTION_DENY) + elif self.comboAction.currentIndex() == 3: + action = "Action = \"{0}\"".format(Config.ACTION_REJECT) + + # FIXME: use prepared statements + if filter_text == "": + if action != "": + qstr += " WHERE " + action + else: + if action != "": + action += " AND " + qstr += " WHERE " + action + " ("\ + " Process LIKE '%" + filter_text + "%'" \ + " OR Destination LIKE '%" + filter_text + "%'" \ + " OR Rule LIKE '%" + filter_text + "%'" \ + " OR Node LIKE '%" + filter_text + "%'" \ + " OR Time LIKE '%" + filter_text + "%'" \ + " OR Protocol LIKE '%" + filter_text + "%')" \ + + qstr += self._get_order() + self._get_limit() + self.setQuery(model, qstr) + + def _set_nodes_query(self, data): + + s = "AND c.src_ip='%s'" % data if '/' not in data else '' + model = self._get_active_table().model() + self.setQuery(model, "SELECT " \ + "MAX(c.time) as {0}, " \ + "c.action as {1}, " \ + "count(c.process) as {2}, " \ + "c.uid as {3}, " \ + "c.protocol as {4}, " \ + "c.dst_ip as {5}, " \ + "c.dst_host as {6}, " \ + "c.dst_port as {7}, " \ + "c.process || ' (' || c.pid || ')' as {8}, " \ + "c.process_args as {9}, " \ + "c.process_cwd as CWD, " \ + "c.rule as {10} " \ + "FROM connections as c " \ + "WHERE c.node LIKE '%{11}%' {12} GROUP BY {13}, c.process_args, c.uid, c.src_ip, c.dst_ip, c.dst_host, c.dst_port, c.protocol {14}".format( + self.COL_STR_TIME, + self.COL_STR_ACTION, + self.COL_STR_HITS, + self.COL_STR_UID, + self.COL_STR_PROTOCOL, + self.COL_STR_DST_IP, + self.COL_STR_DST_HOST, + self.COL_STR_DST_PORT, + self.COL_STR_PROCESS, + self.COL_STR_PROC_ARGS, + self.COL_STR_RULE, + data, s, + self.COL_STR_PROCESS, + self._get_order() + self._get_limit())) + + def _get_nodes_filter_query(self, lastQuery, text): + base_query = lastQuery.split("GROUP BY") + qstr = base_query[0] + if "AND" in qstr: + # strip out ANDs if any + os = qstr.split('AND') + qstr = os[0] + + if text != "": + qstr += "AND (c.time LIKE '%{0}%' OR " \ + "c.action LIKE '%{0}%' OR " \ + "c.pid LIKE '%{0}%' OR " \ + "c.src_port LIKE '%{0}%' OR " \ + "c.dst_port LIKE '%{0}%' OR " \ + "c.src_ip LIKE '%{0}%' OR " \ + "c.dst_ip LIKE '%{0}%' OR " \ + "c.dst_host LIKE '%{0}%' OR " \ + "c.process LIKE '%{0}%' OR " \ + "c.process_args LIKE '%{0}%')".format(text) + if len(base_query) > 1: + qstr += " GROUP BY" + base_query[1] + + return qstr + + def _set_rules_filter(self, parent_row=-1, item_row=0, what=""): + section = self.FILTER_TREE_APPS + + if parent_row == -1: + if item_row == self.RULES_TREE_NODES: + section=self.FILTER_TREE_NODES + what="" + else: + section=self.FILTER_TREE_APPS + what="" + + elif parent_row == self.RULES_TREE_APPS: + if item_row == self.RULES_TREE_PERMANENT: + section=self.FILTER_TREE_APPS + what=self.RULES_TYPE_PERMANENT + elif item_row == self.RULES_TREE_TEMPORARY: + section=self.FILTER_TREE_APPS + what=self.RULES_TYPE_TEMPORARY + + elif parent_row == self.RULES_TREE_NODES: + section=self.FILTER_TREE_NODES + + if section == self.FILTER_TREE_APPS: + if what == self.RULES_TYPE_TEMPORARY: + what = "WHERE r.duration != '%s'" % Config.DURATION_ALWAYS + elif what == self.RULES_TYPE_PERMANENT: + what = "WHERE r.duration = '%s'" % Config.DURATION_ALWAYS + elif section == self.FILTER_TREE_NODES and what != "": + what = "WHERE r.node = '%s'" % what + + filter_text = self.filterLine.text() + if filter_text != "": + if what == "": + what = "WHERE" + else: + what = what + " AND" + what = what + " r.name LIKE '%{0}%'".format(filter_text) + model = self._get_active_table().model() + self.setQuery(model, "SELECT * FROM rules as r %s %s %s" % (what, self._get_order(), self._get_limit())) + self._restore_details_view_columns( + self.TABLES[self.TAB_RULES]['view'].horizontalHeader(), + "{0}{1}".format(Config.STATS_VIEW_COL_STATE, self.TAB_RULES) + ) + + def _set_rules_query(self, rule_name="", node=""): + if node != "": + node = "c.node = '%s'" % node + if rule_name != "": + rule_name = "c.rule = '%s'" % rule_name + + condition = "%s AND %s" % (rule_name, node) if rule_name != "" and node != "" else "" + + model = self._get_active_table().model() + self.setQuery(model, "SELECT " \ + "MAX(c.time) as {0}, " \ + "c.node as {1}, " \ + "count(c.process) as {2}, " \ + "c.uid as {3}, " \ + "c.protocol as {4}, " \ + "c.dst_port as {5}, " \ + "CASE c.dst_host WHEN ''" \ + " THEN c.dst_ip " \ + " ELSE c.dst_host " \ + "END {6}, " \ + "c.process as {7}, " \ + "c.process_args as {8}, " \ + "c.process_cwd as CWD " \ + "FROM connections as c " \ + "WHERE {9} GROUP BY c.process, c.process_args, c.uid, {10}, c.dst_port {11}".format( + self.COL_STR_TIME, + self.COL_STR_NODE, + self.COL_STR_HITS, + self.COL_STR_UID, + self.COL_STR_PROTOCOL, + self.COL_STR_DST_PORT, + self.COL_STR_DESTINATION, + self.COL_STR_PROCESS, + self.COL_STR_PROC_ARGS, + condition, + self.COL_STR_DESTINATION, + self._get_order() + self._get_limit())) + + def _set_hosts_query(self, data): + model = self._get_active_table().model() + self.setQuery(model, "SELECT " \ + "MAX(c.time) as {0}, " \ + "c.node as {1}, " \ + "count(c.process) as {2}, " \ + "c.action as {3}, " \ + "c.uid as {4}, " \ + "c.protocol as {5}, " \ + "c.dst_port as {6}, " \ + "c.dst_ip as {7}, " \ + "c.process || ' (' || c.pid || ')' as {8}, " \ + "c.process_args as {9}, " \ + "c.process_cwd as CWD, " \ + "c.rule as {10} " \ + "FROM connections as c " \ + "WHERE c.dst_host = '{11}' GROUP BY c.pid, {12}, c.process_args, c.src_ip, c.dst_ip, c.dst_port, c.protocol, c.action, c.node {13}".format( + self.COL_STR_TIME, + self.COL_STR_NODE, + self.COL_STR_HITS, + self.COL_STR_ACTION, + self.COL_STR_UID, + self.COL_STR_PROTOCOL, + self.COL_STR_DST_PORT, + self.COL_STR_DST_IP, + self.COL_STR_PROCESS, + self.COL_STR_PROC_ARGS, + self.COL_STR_RULE, + data, + self.COL_STR_PROCESS, + self._get_order("1") + self._get_limit())) + + def _set_process_query(self, data): + model = self._get_active_table().model() + self.setQuery(model, "SELECT " \ + "MAX(c.time) as {0}, " \ + "c.node as {1}, " \ + "count(c.dst_ip) as {2}, " \ + "c.action as {3}, " \ + "c.uid as {4}, " \ + "CASE c.dst_host WHEN ''" \ + " THEN c.dst_ip || ' -> ' || c.dst_port " \ + " ELSE c.dst_host || ' -> ' || c.dst_port " \ + "END {5}, " \ + "c.pid as PID, " \ + "c.process_args as {6}, " \ + "c.process_cwd as CWD, " \ + "c.rule as {7} " \ + "FROM connections as c " \ + "WHERE c.process = '{8}' " \ + "GROUP BY c.src_ip, c.dst_ip, c.dst_host, c.dst_port, c.uid, c.action, c.node, c.pid, c.process_args {9}".format( + self.COL_STR_TIME, + self.COL_STR_NODE, + self.COL_STR_HITS, + self.COL_STR_ACTION, + self.COL_STR_UID, + self.COL_STR_DESTINATION, + self.COL_STR_PROC_ARGS, + self.COL_STR_RULE, + data, + self._get_order("1") + self._get_limit())) + + nrows = self._get_active_table().model().rowCount() + self.cmdProcDetails.setVisible(nrows != 0) + + def _set_addrs_query(self, data): + model = self._get_active_table().model() + self.setQuery(model, "SELECT " \ + "MAX(c.time) as {0}, " \ + "c.node as {1}, " \ + "count(c.dst_ip) as {2}, " \ + "c.action as {3}, " \ + "c.uid as {4}, " \ + "c.protocol as {5}, " \ + "CASE c.dst_host WHEN ''" \ + " THEN c.dst_ip " \ + " ELSE c.dst_host " \ + "END {6}, " \ + "c.dst_port as {7}, " \ + "c.process || ' (' || c.pid || ')' as {8}, " \ + "c.process_args as {9}, " \ + "c.process_cwd as CWD, " \ + "c.rule as {10} " \ + "FROM connections as c " \ + "WHERE c.dst_ip = '{11}' GROUP BY c.pid, {12}, c.process_args, c.src_ip, c.dst_port, {13}, c.protocol, c.action, c.uid, c.node {14}".format( + self.COL_STR_TIME, + self.COL_STR_NODE, + self.COL_STR_HITS, + self.COL_STR_ACTION, + self.COL_STR_UID, + self.COL_STR_PROTOCOL, + self.COL_STR_DESTINATION, + self.COL_STR_DST_PORT, + self.COL_STR_PROCESS, + self.COL_STR_PROC_ARGS, + self.COL_STR_RULE, + data, + self.COL_STR_PROCESS, + self.COL_STR_DESTINATION, + self._get_order("1") + self._get_limit())) + + def _set_ports_query(self, data): + model = self._get_active_table().model() + self.setQuery(model, "SELECT " \ + "MAX(c.time) as {0}, " \ + "c.node as {1}, " \ + "count(c.dst_ip) as {2}, " \ + "c.action as {3}, " \ + "c.uid as {4}, " \ + "c.protocol as {5}, " \ + "c.dst_ip as {6}, " \ + "CASE c.dst_host WHEN ''" \ + " THEN c.dst_ip " \ + " ELSE c.dst_host " \ + "END {7}, " \ + "c.process || ' (' || c.pid || ')' as {8}, " \ + "c.process_args as {9}, " \ + "c.process_cwd as CWD, " \ + "c.rule as {10} " \ + "FROM connections as c " \ + "WHERE c.dst_port = '{11}' GROUP BY c.pid, {12}, c.process_args, {13}, c.src_ip, c.dst_ip, c.protocol, c.action, c.uid, c.node {14}".format( + self.COL_STR_TIME, + self.COL_STR_NODE, + self.COL_STR_HITS, + self.COL_STR_ACTION, + self.COL_STR_UID, + self.COL_STR_PROTOCOL, + self.COL_STR_DST_IP, + self.COL_STR_DESTINATION, + self.COL_STR_PROCESS, + self.COL_STR_PROC_ARGS, + self.COL_STR_RULE, + data, + self.COL_STR_PROCESS, + self.COL_STR_DESTINATION, + self._get_order("1") + self._get_limit())) + + def _set_users_query(self, data): + uid = data.split(" ") + if len(uid) == 2: + uid = uid[1].strip("()") + else: + uid = uid[0] + model = self._get_active_table().model() + self.setQuery(model, "SELECT " \ + "MAX(c.time) as {0}, " \ + "c.uid, " \ + "c.node as {1}, " \ + "count(c.dst_ip) as {2}, " \ + "c.action as {3}, " \ + "c.protocol as {4}, " \ + "c.dst_ip as {5}, " \ + "c.dst_host as {6}, " \ + "c.dst_port as {7}, " \ + "c.process || ' (' || c.pid || ')' as {8}, " \ + "c.process_args as {9}, " \ + "c.process_cwd as CWD, " \ + "c.rule as {10} " \ + "FROM connections as c " \ + "WHERE c.uid = '{11}' GROUP BY c.pid, {12}, c.process_args, c.src_ip, c.dst_ip, c.dst_host, c.dst_port, c.protocol, c.action, c.node {13}".format( + self.COL_STR_TIME, + self.COL_STR_NODE, + self.COL_STR_HITS, + self.COL_STR_ACTION, + self.COL_STR_PROTOCOL, + self.COL_STR_DST_IP, + self.COL_STR_DESTINATION, + self.COL_STR_DST_PORT, + self.COL_STR_PROCESS, + self.COL_STR_PROC_ARGS, + self.COL_STR_RULE, + uid, + self.COL_STR_PROCESS, + self._get_order("1") + self._get_limit())) + + # get the query filtering by text when a tab is in the detail view. + def _get_indetail_filter_query(self, lastQuery, text): + try: + cur_idx = self.tabWidget.currentIndex() + base_query = lastQuery.split("GROUP BY") + qstr = base_query[0] + where = qstr.split("WHERE")[1] # get SELECT ... WHERE (*) + ands = where.split("AND (")[0] # get WHERE (*) AND (...) + qstr = qstr.split("WHERE")[0] # get * WHERE ... + qstr += "WHERE %s" % ands + + # if there's no text to filter, strip the filter "AND ()", and + # return the original query. + if text == "": + return + + qstr += "AND (c.time LIKE '%{0}%' OR " \ + "c.action LIKE '%{0}%' OR " \ + "c.pid LIKE '%{0}%' OR " \ + "c.src_port LIKE '%{0}%' OR " \ + "c.src_ip LIKE '%{0}%' OR ".format(text) + + # exclude from query the field of the view we're filtering by + if self.IN_DETAIL_VIEW[cur_idx] != self.TAB_PORTS: + qstr += "c.dst_port LIKE '%{0}%' OR ".format(text) + if self.IN_DETAIL_VIEW[cur_idx] != self.TAB_ADDRS: + qstr += "c.dst_ip LIKE '%{0}%' OR ".format(text) + if self.IN_DETAIL_VIEW[cur_idx] != self.TAB_HOSTS: + qstr += "c.dst_host LIKE '%{0}%' OR ".format(text) + if self.IN_DETAIL_VIEW[cur_idx] != self.TAB_PROCS: + qstr += "c.process LIKE '%{0}%' OR ".format(text) + + qstr += "c.process_args LIKE '%{0}%')".format(text) + + finally: + if len(base_query) > 1: + qstr += " GROUP BY" + base_query[1] + return qstr + + @QtCore.pyqtSlot() + def _on_settings_saved(self): + self.settings_saved.emit() + + def _on_save_clicked(self): + tab_idx = self.tabWidget.currentIndex() + + filename = QtWidgets.QFileDialog.getSaveFileName(self, + QC.translate("stats", 'Save as CSV'), + self._file_names[tab_idx], + 'All Files (*);;CSV Files (*.csv)')[0].strip() + if filename == '': + return + + with self._lock: + table = self._tables[tab_idx] + ncols = table.model().columnCount() + nrows = table.model().rowCount() + cols = [] + + for col in range(0, ncols): + cols.append(table.model().headerData(col, QtCore.Qt.Horizontal)) + + with open(filename, 'w') as csvfile: + w = csv.writer(csvfile, dialect='excel') + w.writerow(cols) + + if tab_idx == self.TAB_MAIN: + w.writerows(table.model().dumpRows()) + else: + for row in range(0, nrows): + values = [] + for col in range(0, ncols): + values.append(table.model().index(row, col).data()) + w.writerow(values) + + def _setup_table(self, widget, tableWidget, table_name, fields="*", group_by="", order_by="2", sort_direction=SORT_ORDER[1], limit="", resize_cols=(), model=None, delegate=None, verticalScrollBar=None): + tableWidget.setSortingEnabled(True) + if model == None: + model = self._db.get_new_qsql_model() + if delegate != None: + tableWidget.setItemDelegate(ColorizedDelegate(self, config=delegate)) + + if verticalScrollBar != None: + tableWidget.setVerticalScrollBar(verticalScrollBar) + tableWidget.vScrollBar.sliderPressed.connect(self._cb_scrollbar_pressed) + tableWidget.vScrollBar.sliderReleased.connect(self._cb_scrollbar_released) + + self.setQuery(model, "SELECT " + fields + " FROM " + table_name + group_by + " ORDER BY " + order_by + " " + sort_direction + limit) + tableWidget.setModel(model) + + header = tableWidget.horizontalHeader() + if header != None: + header.sortIndicatorChanged.connect(self._cb_table_header_clicked) + + for _, col in enumerate(resize_cols): + header.setSectionResizeMode(col, QtWidgets.QHeaderView.ResizeToContents) + + cur_idx = self.tabWidget.currentIndex() + self._cfg.setSettings("{0}{1}".format(Config.STATS_VIEW_DETAILS_COL_STATE, cur_idx), header.saveState()) + return tableWidget + + def update_interception_status(self, enabled): + self.startButton.setDown(enabled) + self.startButton.setChecked(enabled) + if enabled: + self._update_status_label(running=True, text=self.FIREWALL_RUNNING) + else: + self._update_status_label(running=False, text=self.FIREWALL_DISABLED) + + # launched from a thread + def update(self, is_local=True, stats=None, need_query_update=True): + # lock mandatory when there're multiple clients + with self._lock: + if stats is not None: + self._stats = stats + # do not update any tab if the window is not visible + if self.isVisible() and self.isMinimized() == False: + self._trigger.emit(is_local, need_query_update) + + def update_status(self): + self.startButton.setDown(self.daemon_connected) + self.startButton.setChecked(self.daemon_connected) + self.startButton.setDisabled(not self.daemon_connected) + if self.daemon_connected: + self._update_status_label(running=True, text=self.FIREWALL_RUNNING) + else: + self._update_status_label(running=False, text=self.FIREWALL_STOPPED) + self.statusLabel.setStyleSheet('color: red; margin: 5px') + + @QtCore.pyqtSlot(bool, bool) + def _on_update_triggered(self, is_local, need_query_update=False): + if self._stats is None: + self.daemonVerLabel.setText("") + self.uptimeLabel.setText("") + self.rulesLabel.setText("") + self.consLabel.setText("") + self.droppedLabel.setText("") + else: + nodes = self._nodes.count() + self.daemonVerLabel.setText(self._stats.daemon_version) + if nodes <= 1: + self.uptimeLabel.setText(str(datetime.timedelta(seconds=self._stats.uptime))) + self.rulesLabel.setText("%s" % self._stats.rules) + self.consLabel.setText("%s" % self._stats.connections) + self.droppedLabel.setText("%s" % self._stats.dropped) + else: + self.uptimeLabel.setText("") + self.rulesLabel.setText("") + self.consLabel.setText("") + self.droppedLabel.setText("") + + if need_query_update: + self._refresh_active_table() + + # prevent a click on the window's x + # from quitting the whole application + def closeEvent(self, e): + self._save_settings() + e.accept() + self.hide() + + def hideEvent(self, e): + self._save_settings() + + # https://gis.stackexchange.com/questions/86398/how-to-disable-the-escape-key-for-a-dialog + def keyPressEvent(self, event): + if not event.key() == QtCore.Qt.Key_Escape: + super(StatsDialog, self).keyPressEvent(event) + + def setQuery(self, model, q): + if self._context_menu_active == True or self.scrollbar_active == True or self._are_rows_selected(): + return + with self._lock: + try: + model.query().clear() + model.setQuery(q, self._db_sqlite) + if model.lastError().isValid(): + print("setQuery() error: ", model.lastError().text()) + + if self.tabWidget.currentIndex() != self.TAB_MAIN: + self.labelRowsCount.setText("{0}".format(model.rowCount())) + else: + self.labelRowsCount.setText("") + except Exception as e: + print(self._address, "setQuery() exception: ", e) + finally: + self._show_columns() diff --git a/ui/opensnitch/nodes.py b/ui/opensnitch/nodes.py new file mode 100644 index 0000000..2315da3 --- /dev/null +++ b/ui/opensnitch/nodes.py @@ -0,0 +1,308 @@ +from queue import Queue +from datetime import datetime +import time +import json + +from opensnitch import ui_pb2 +from opensnitch.database import Database +from opensnitch.config import Config + +class Nodes(): + __instance = None + LOG_TAG = "[Nodes]: " + ONLINE = "\u2713 online" + OFFLINE = "\u2613 offline" + WARNING = "\u26a0" + + @staticmethod + def instance(): + if Nodes.__instance == None: + Nodes.__instance = Nodes() + return Nodes.__instance + + def __init__(self): + self._db = Database.instance() + self._nodes = {} + self._notifications_sent = {} + + def count(self): + return len(self._nodes) + + def add(self, peer, client_config=None): + try: + proto, _addr = self.get_addr(peer) + addr = "%s:%s" % (proto, _addr) + if addr not in self._nodes: + self._nodes[addr] = { + 'notifications': Queue(), + 'online': True, + 'last_seen': datetime.now() + } + else: + self._nodes[addr]['last_seen'] = datetime.now() + + self._nodes[addr]['online'] = True + self.add_data(addr, client_config) + self.update(proto, _addr) + + return self._nodes[addr] + + except Exception as e: + print(self.LOG_TAG, "exception adding/updating node: ", e, "addr:", addr, "config:", client_config) + + return None + + def add_data(self, addr, client_config): + if client_config != None: + self._nodes[addr]['data'] = self.get_client_config(client_config) + self.add_rules(addr, client_config.rules) + + def add_rule(self, time, node, name, enabled, precedence, action, duration, op_type, op_sensitive, op_operand, op_data): + # don't add rule if the user has selected to exclude temporary + # rules + if duration in Config.RULES_DURATION_FILTER: + return + + self._db.insert("rules", + "(time, node, name, enabled, precedence, action, duration, operator_type, operator_sensitive, operator_operand, operator_data)", + (time, node, name, enabled, precedence, action, duration, op_type, op_sensitive, op_operand, op_data), + action_on_conflict="REPLACE") + + def add_rules(self, addr, rules): + try: + for _,r in enumerate(rules): + self.add_rule(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + addr, + r.name, str(r.enabled), str(r.precedence), r.action, r.duration, + r.operator.type, + str(r.operator.sensitive), + r.operator.operand, + r.operator.data) + except Exception as e: + print(self.LOG_TAG + " exception adding node to db: ", e) + + def update_rule_time(self, time, rule_name, addr): + self._db.update("rules", + "time=?", + (time, rule_name, addr), + "name=? AND node=?", + action_on_conflict="OR REPLACE" + ) + + def delete_all(self): + self.send_notifications(None) + self._nodes = {} + + def delete(self, peer): + proto, addr = self.get_addr(peer) + addr = "%s:%s" % (proto, addr) + # Force the node to get one new item from queue, + # in order to loop and exit. + self._nodes[addr]['notifications'].put(None) + if addr in self._nodes: + del self._nodes[addr] + + def get(self): + return self._nodes + + def get_node(self, addr): + try: + return self._nodes[addr] + except Exception as e: + return None + + def get_nodes(self): + return self._nodes + + def get_node_config(self, addr): + try: + return self._nodes[addr]['data'].config + except Exception as e: + print(self.LOG_TAG + " exception get_node_config(): ", e) + return None + + def get_client_config(self, client_config): + try: + node_config = json.loads(client_config.config) + if 'LogLevel' not in node_config: + node_config['LogLevel'] = 1 + client_config.config = json.dumps(node_config) + except Exception as e: + print(self.LOG_TAG, "exception parsing client config", e) + + return client_config + + def get_addr(self, peer): + peer = peer.split(":") + # WA for backward compatibility + if peer[0] == "unix" and peer[1] == "": + peer[1] = "/local" + return peer[0], peer[1] + + def get_notifications(self): + notlist = [] + try: + for c in self._nodes: + if self._nodes[c]['online'] == False: + continue + if self._nodes[c]['notifications'].empty(): + continue + notif = self._nodes[c]['notifications'].get(False) + if notif != None: + self._nodes[c]['notifications'].task_done() + notlist.append(notif) + except Exception as e: + print(self.LOG_TAG + " exception get_notifications(): ", e) + + return notlist + + def save_node_config(self, addr, config): + try: + self._nodes[addr]['data'].config = config + except Exception as e: + print(self.LOG_TAG + " exception saving node config: ", e, addr, config) + + def save_nodes_config(self, config): + try: + for c in self._nodes: + self._nodes[c]['data'].config = config + except Exception as e: + print(self.LOG_TAG + " exception saving nodes config: ", e, config) + + def start_interception(self, _addr=None, _callback=None): + return self.firewall(not_type=ui_pb2.LOAD_FIREWALL, addr=_addr, callback=_callback) + + def stop_interception(self, _addr=None, _callback=None): + return self.firewall(not_type=ui_pb2.UNLOAD_FIREWALL, addr=_addr, callback=_callback) + + def firewall(self, not_type=ui_pb2.LOAD_FIREWALL, addr=None, callback=None): + noti = ui_pb2.Notification(clientName="", serverName="", type=not_type, data="", rules=[]) + if addr == None: + nid = self.send_notifications(noti, callback) + else: + nid = self.send_notification(addr, noti, callback) + + return nid, noti + + def send_notification(self, addr, notification, callback_signal=None): + try: + notification.id = int(str(time.time()).replace(".", "")) + if addr not in self._nodes: + # FIXME: the reply is sent before we return the notification id + if callback_signal != None: + callback_signal.emit( + ui_pb2.NotificationReply( + id=notification.id, + code=ui_pb2.ERROR, + data="node not connected: {0}".format(addr) + ) + ) + return notification.id + + self._notifications_sent[notification.id] = { + 'callback': callback_signal, + 'type': notification.type + } + self._nodes[addr]['notifications'].put(notification) + except Exception as e: + print(self.LOG_TAG + " exception sending notification: ", e, addr, notification) + if callback_signal != None: + callback_signal.emit( + ui_pb2.NotificationReply( + id=notification.id, + code=ui_pb2.ERROR, + data="Notification not sent ({0}):<br>{1}".format(addr, e) + ) + ) + + return notification.id + + def send_notifications(self, notification, callback_signal=None): + """ + Enqueues a notification to the clients queue. + It'll be retrieved and delivered by get_notifications + """ + try: + notification.id = int(str(time.time()).replace(".", "")) + for c in self._nodes: + self._nodes[c]['notifications'].put(notification) + self._notifications_sent[notification.id] = { + 'callback': callback_signal, + 'type': notification.type + } + except Exception as e: + print(self.LOG_TAG + " exception sending notifications: ", e, notification) + + return notification.id + + def reply_notification(self, addr, reply): + if reply == None: + print(self.LOG_TAG, " reply notification None") + return + + if reply.id in self._notifications_sent: + if self._notifications_sent[reply.id] != None: + if self._notifications_sent[reply.id]['callback'] != None: + self._notifications_sent[reply.id]['callback'].emit(reply) + + # delete only one-time notifications + # we need the ID of streaming notifications from the server + # (monitor_process for example) to keep track of the data sent to us. + if self._notifications_sent[reply.id]['type'] != ui_pb2.MONITOR_PROCESS: + del self._notifications_sent[reply.id] + + def stop_notifications(self): + """Send a dummy notification to force Notifications class to exit. + """ + exit_noti = ui_pb2.Notification(clientName="", serverName="", type=0, data="", rules=[]) + self.send_notifications(exit_noti) + + def update(self, proto, addr, status=ONLINE): + try: + self._db.update("nodes", + "hostname=?,version=?,last_connection=?,status=?", + ( + self._nodes[proto+":"+addr]['data'].name, + self._nodes[proto+":"+addr]['data'].version, + datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + status, + addr), + "addr=?" + ) + except Exception as e: + print(self.LOG_TAG + " exception updating DB: ", e, addr) + + def update_all(self, status=OFFLINE): + try: + for peer in self._nodes: + proto, addr = self.get_addr(peer) + self._db.update("nodes", + "hostname=?,version=?,last_connection=?,status=?", + ( + self._nodes[proto+":"+addr]['data'].name, + self._nodes[proto+":"+addr]['data'].version, + datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + status, + addr), + "addr=?" + ) + except Exception as e: + print(self.LOG_TAG + " exception updating nodes: ", e) + + def delete_rule(self, rule_name, addr, callback): + rule = ui_pb2.Rule(name=rule_name) + rule.enabled = False + rule.action = "" + rule.duration = "" + rule.operator.type = "" + rule.operator.operand = "" + rule.operator.data = "" + + noti = ui_pb2.Notification(type=ui_pb2.DELETE_RULE, rules=[rule]) + if addr != None: + nid = self.send_notification(addr, noti, None) + else: + nid = self.send_notifications(noti, None) + self._db.delete_rule(rule.name, addr) + + return nid, noti diff --git a/ui/opensnitch/notifications.py b/ui/opensnitch/notifications.py new file mode 100644 index 0000000..72754e2 --- /dev/null +++ b/ui/opensnitch/notifications.py @@ -0,0 +1,126 @@ +#!/usr/bin/python + +from PyQt5.QtCore import QCoreApplication as QC +import os +from opensnitch.utils import Utils +from opensnitch.config import Config + +class DesktopNotifications(): + """DesktopNotifications display informative pop-ups using the system D-Bus. + The notifications are handled and configured by the system. + + The notification daemon also decides where to show the notifications, as well + as how to group them. + + The body of a notification supports markup (if the implementation supports it): + https://people.gnome.org/~mccann/docs/notification-spec/notification-spec-latest.html#markup + Basically: <a>, <u>, <b>, <i> and <img>. New lines can be added with the regular \n. + + It also support actions (buttons). + + https://notify2.readthedocs.io/en/latest/ + """ + + _cfg = Config.init() + + # list of hints: + # https://people.gnome.org/~mccann/docs/notification-spec/notification-spec-latest.html#hints + HINT_DESKTOP_ENTRY = "desktop-entry" + CATEGORY_NETWORK = "network" + + EXPIRES_DEFAULT = 0 + NEVER_EXPIRES = -1 + + def __init__(self): + self.ACTION_ALLOW = QC.translate("popups", "Allow") + self.ACTION_DENY = QC.translate("popups", "Deny") + self.IS_LIBNOTIFY_AVAILABLE = True + self.DOES_SUPPORT_ACTIONS = True + + try: + import notify2 + self.ntf2 = notify2 + mloop = 'glib' + + # First try to initialise the D-Bus connection with the given + # mainloop. + # If it fails, we'll try to initialise it without it. + try: + self.ntf2.init("opensnitch", mainloop=mloop) + except Exception: + self.DOES_SUPPORT_ACTIONS = False + self.ntf2.init("opensnitch") + + # usually because dbus mainloop is not initiated, specially + # with 'qt' + # FIXME: figure out how to init it, or how to connect to an + # existing session. + print("DesktopNotifications(): system doesn't support actions. Available capabilities:") + print(self.ntf2.get_server_caps()) + + + # Example: ['actions', 'action-icons', 'body', 'body-markup', 'icon-static', 'persistence', 'sound'] + if ('actions' not in self.ntf2.get_server_caps()): + self.DOES_SUPPORT_ACTIONS = False + + except Exception as e: + print("DesktopNotifications not available (install python3-notify2):", e) + self.IS_LIBNOTIFY_AVAILABLE = False + + def is_available(self): + return self.IS_LIBNOTIFY_AVAILABLE + + def are_enabled(self): + return self._cfg.getBool(Config.NOTIFICATIONS_ENABLED, True) + + def support_actions(self): + """Returns true if the notifications daemon support actions(buttons). + This depends on 2 factors: + - If the notification server actually supports it (get_server_caps()). + - If there's a dbus instance running. + """ + return self.DOES_SUPPORT_ACTIONS + + def show(self, title, body, icon="dialog-information"): + try: + ntf = self.ntf2.Notification(title, body, icon) + + # timeouts seems to be ignored (on Cinnamon at least) + timeout = self._cfg.getInt(Config.DEFAULT_TIMEOUT_KEY, 15) + # -1 and 0 are special values + if timeout > 0: + timeout = timeout * 1000 + ntf.set_timeout(timeout * 1000) + ntf.timeout = timeout * 1000 + + ntf.set_category(self.CATEGORY_NETWORK) + # used to display our app icon an name. + ntf.set_hint(self.HINT_DESKTOP_ENTRY, "opensnitch_ui") + ntf.show() + except Exception as e: + print("[notifications] show() exception:", e) + + # TODO: + # - construct a rule with the default configured parameters. + # - create a common dialogs/prompt.py:_send_rule(), maybe in utils.py + def ask(self, connection, timeout, callback): + c = connection + title = QC.translate("popups", "New outgoing connection") + body = c.process_path + "\n" + body = body + QC.translate("popups", "is connecting to <b>%s</b> on %s port %d") % ( \ + c.dst_host or c.dst_ip, + c.protocol.upper(), + c.dst_port ) + + ntf = self.ntf2.Notification(title, body, "dialog-warning") + timeout = self._cfg.getInt(Config.DEFAULT_TIMEOUT_KEY, 15) + ntf.set_timeout(timeout * 1000) + ntf.timeout = timeout * 1000 + if self.DOES_SUPPORT_ACTIONS: + ntf.set_urgency(self.ntf2.URGENCY_CRITICAL) + ntf.add_action("allow", self.ACTION_ALLOW, callback, connection) + ntf.add_action("deny", self.ACTION_DENY, callback, connection) + #ntf.add_action("open-gui", QC.translate("popups", "View"), callback, connection) + ntf.set_category(self.CATEGORY_NETWORK) + ntf.set_hint(self.HINT_DESKTOP_ENTRY, "opensnitch_ui") + ntf.show() diff --git a/ui/opensnitch/res/__init__.py b/ui/opensnitch/res/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ui/opensnitch/res/icon-alert.png b/ui/opensnitch/res/icon-alert.png new file mode 100644 index 0000000000000000000000000000000000000000..77cb571c22e2027b427af30540b0c42a741efdbf GIT binary patch literal 19598 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 literal 0 HcmV?d00001 diff --git a/ui/opensnitch/res/icon-off.png b/ui/opensnitch/res/icon-off.png new file mode 100644 index 0000000000000000000000000000000000000000..e33acedf0637b804aa4654cd71c2a14b0b5a0cb3 GIT binary patch literal 20195 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 Vf#5BnxCoH<JYD@<);T3K0RX9LDeeFO literal 0 HcmV?d00001 diff --git a/ui/opensnitch/res/icon-pause.png b/ui/opensnitch/res/icon-pause.png new file mode 100644 index 0000000000000000000000000000000000000000..436df98409f0dc109136c931616baff2d83722d6 GIT binary patch literal 16530 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 UKa!>Yf?q+}8oH<=HH(-3AA3;HG5`Po literal 0 HcmV?d00001 diff --git a/ui/opensnitch/res/icon-pause.svg b/ui/opensnitch/res/icon-pause.svg new file mode 100644 index 0000000..9f61111 --- /dev/null +++ b/ui/opensnitch/res/icon-pause.svg @@ -0,0 +1,109 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="512" + height="512" + viewBox="0 0 135.46667 135.46667" + version="1.1" + id="svg8" + inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" + sodipodi:docname="icon-pause.svg" + inkscape:export-filename="/home/ga/Proiektuak/opensnitch/gui/opensnitch/ui/opensnitch/res/icon-pause.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <defs + id="defs2" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1.8520834" + inkscape:cx="92.868389" + inkscape:cy="235.9505" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="false" + inkscape:window-width="1600" + inkscape:window-height="847" + inkscape:window-x="0" + inkscape:window-y="204" + inkscape:window-maximized="1" + units="px" + inkscape:document-rotation="0" + showguides="true" + inkscape:guide-bbox="true" + inkscape:pagecheckerboard="0"> + <sodipodi:guide + position="45.643586,19.473337" + orientation="0,-1" + id="guide858" /> + <sodipodi:guide + position="48.574825,118.50736" + orientation="0,-1" + id="guide860" /> + </sodipodi:namedview> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Capa 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(0,-284.30001)"> + <path + style="fill:#232629;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.88352px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 24.756487,346.52813 9.319538,-24.69115 25.892395,-5.7321 24.204196,-12.72968 25.173834,11.71117 4.05241,24.92736 16.3729,12.59899 3.85152,17.78296 -6.72818,16.22266 -13.39906,8.50947 -91.980588,0.9444 -15.4585065,-11.35245 -3.13783,-17.10855 7.8853195,-15.623 z" + id="path1481" + sodipodi:nodetypes="ccccccccccccccc" + inkscape:export-filename="/home/ga/Proiektuak/opensnitch/gui/opensnitch/ui/lib/python3.9/site-packages/opensnitch/res/icon-white.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" /> + <path + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.260914;stroke-opacity:1" + d="M 25.617431,400.30128 C 13.956756,398.62993 4.455433,390.16178 1.3179549,378.64484 c -0.61010168,-2.23995 -0.68141103,-2.97449 -0.68141103,-7.02809 0,-4.05349 0.0709898,-4.78804 0.68141103,-7.02767 1.4422687,-5.29433 4.0049287,-9.7113 7.7967237,-13.43793 3.1792184,-3.12477 7.6935334,-5.90721 11.3127154,-6.97315 l 1.011141,-0.29572 0.159592,-2.88031 c 0.699721,-12.63666 9.525535,-23.14365 22.243579,-26.48047 2.251529,-0.59144 3.06051,-0.67035 7.018915,-0.67878 2.525469,-0.006 4.987882,0.11997 5.653762,0.28414 1.179544,0.29572 1.179787,0.29572 1.94729,-0.66193 1.527585,-1.90257 4.987766,-4.99031 7.28526,-6.50045 5.401898,-3.5513 10.887485,-5.3302 17.382981,-5.63665 5.770238,-0.26941 10.984996,0.78506 16.16673,3.27379 7.128836,3.42523 12.181556,8.39142 15.666736,15.39805 2.50603,5.03807 3.59513,10.17644 3.34371,15.77554 l -0.13051,2.91272 2.13481,1.38154 c 1.17413,0.7598 3.3948,2.61049 4.93483,4.112 8.71561,8.49782 11.93955,20.33805 8.75495,32.15396 -1.29904,4.81951 -4.90315,10.89827 -8.63914,14.56995 -3.56956,3.50836 -9.80593,7.14911 -14.36425,8.38564 -4.82636,1.30967 -4.22578,1.29325 -45.398785,1.26083 -21.133453,-0.021 -39.125022,-0.13154 -39.981254,-0.25467 z m 81.947239,-8.71712 c 5.8504,-1.53751 11.05542,-5.03703 14.56544,-9.79276 1.44781,-1.96191 3.2449,-5.86701 3.9156,-8.50866 0.82755,-3.25884 0.82928,-8.34365 0.004,-11.5926 -1.99124,-7.83999 -8.14631,-14.63425 -15.64619,-17.27125 -1.49953,-0.52724 -2.28209,-0.9303 -2.20209,-1.1355 2.47536,-6.33996 2.20303,-13.3187 -0.75958,-19.46544 -2.59443,-5.38335 -6.55609,-9.27909 -12.007089,-11.8076 -3.90889,-1.81322 -6.232969,-2.31309 -10.75536,-2.31309 -3.127696,0 -4.219681,0.10629 -6.053166,0.58617 -7.241285,1.89288 -13.31075,6.68923 -16.465647,13.01161 l -0.732485,1.46794 -1.204478,-0.5988 c -2.240975,-1.11298 -5.542791,-1.97328 -8.178424,-2.13124 -9.130628,-0.54723 -18.012897,5.39587 -20.93206,14.00441 -1.653072,4.87486 -1.486231,9.52955 0.524816,14.64108 0.134869,0.34097 -0.08673,0.38411 -1.535235,0.29256 -2.040978,-0.12839 -5.359579,0.4904 -7.966056,1.4852 -5.358562,2.0439 -10.218999,7.06398 -12.038096,12.43418 -1.6865107,4.9784 -1.5773642,9.50883 0.346691,14.39252 2.080429,5.27991 7.182215,10.04153 12.692712,11.84623 1.10934,0.36201 2.648952,0.76296 3.421359,0.8924 0.794802,0.13259 18.196341,0.21573 40.092031,0.19047 l 38.687677,-0.0421 z" + id="path826" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccsccccccccccccsccccccccccccccscccsccccsscccccccccc" /> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:76.6842px;line-height:47.9278px;font-family:Korataki;-inkscape-font-specification:Korataki;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.91711px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="34.512127" + y="456.6312" + id="text841" + transform="scale(1.1975484,0.83503932)"><tspan + sodipodi:role="line" + id="tspan839" + x="34.512127" + y="456.6312" + style="fill:#ffffff;stroke-width:1.91711px">I</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:76.6842px;line-height:47.9278px;font-family:Korataki;-inkscape-font-specification:Korataki;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.91711px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="61.164921" + y="456.6312" + id="text841-7" + transform="scale(1.1975484,0.83503932)"><tspan + sodipodi:role="line" + id="tspan839-6" + x="61.164921" + y="456.6312" + style="fill:#ffffff;stroke-width:1.91711px">I</tspan></text> + </g> +</svg> diff --git a/ui/opensnitch/res/icon-red.png b/ui/opensnitch/res/icon-red.png new file mode 100644 index 0000000000000000000000000000000000000000..d495251a65f532f4b2aeea1b7d4bfba083b77e64 GIT binary patch literal 3858 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 literal 0 HcmV?d00001 diff --git a/ui/opensnitch/res/icon-white.png b/ui/opensnitch/res/icon-white.png new file mode 100644 index 0000000000000000000000000000000000000000..dae15872eb898abebf93815a37a4f1f057742e1b GIT binary patch literal 20760 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 PhhhL>g|aNa;2rm09_3#h literal 0 HcmV?d00001 diff --git a/ui/opensnitch/res/icon-white.svg b/ui/opensnitch/res/icon-white.svg new file mode 100644 index 0000000..a0e5a53 --- /dev/null +++ b/ui/opensnitch/res/icon-white.svg @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="512" + height="512" + viewBox="0 0 135.46667 135.46667" + version="1.1" + id="svg8" + inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" + sodipodi:docname="icon-white.svg" + inkscape:export-filename="/home/ga/Proiektuak/opensnitch/gui/opensnitch/ui/lib/python3.9/site-packages/opensnitch/res/icon-white.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <defs + id="defs2" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.9899495" + inkscape:cx="288.90363" + inkscape:cy="223.24371" + inkscape:document-units="px" + inkscape:current-layer="g3307" + showgrid="false" + inkscape:window-width="1600" + inkscape:window-height="847" + inkscape:window-x="0" + inkscape:window-y="204" + inkscape:window-maximized="1" + units="px" + inkscape:pagecheckerboard="0" + showguides="true" + inkscape:guide-bbox="true" /> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Capa 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(0,-284.30001)"> + <g + id="g3307" + transform="matrix(10.756234,0,0,10.756234,-0.76070676,-2776.4763)"> + <path + style="fill:#232629;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.268079px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 2.4759494,290.29453 0.8664313,-2.29552 2.4071989,-0.53291 2.2502482,-1.18347 2.3403932,1.08878 0.37675,2.31748 1.522178,1.17132 0.358073,1.65327 -0.625514,1.50821 -1.245702,0.79112 -8.5513735,0.0878 -1.43716703,-1.05543 -0.29172201,-1.59057 0.73309294,-1.45246 z" + id="path1481" + sodipodi:nodetypes="ccccccccccccccc" + inkscape:export-filename="/home/ga/Proiektuak/opensnitch/gui/opensnitch/ui/lib/python3.9/site-packages/opensnitch/res/icon-white.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" /> + <path + style="fill:#fbffff;fill-opacity:1;stroke:none;stroke-width:0.0245664;stroke-opacity:1" + d="m 2.4221019,295.34968 c -1.0878659,-0.15882 -1.97427807,-0.9635 -2.26698456,-2.05789 -0.05691855,-0.21285 -0.06357126,-0.28265 -0.06357126,-0.66784 0,-0.38518 0.0066228,-0.45498 0.06357126,-0.6678 0.13455437,-0.50309 0.37363404,-0.92281 0.72738412,-1.27693 0.29660044,-0.29693 0.71775704,-0.56133 1.05540334,-0.66262 l 0.094333,-0.0281 0.014889,-0.2737 c 0.065279,-1.20079 0.888671,-2.19921 2.0751826,-2.51629 0.2100531,-0.0562 0.2855258,-0.0637 0.6548195,-0.0645 0.2356099,-5.9e-4 0.4653373,0.0114 0.5274596,0.027 0.1100438,0.0281 0.1100666,0.0281 0.1816696,-0.0629 0.1425139,-0.18079 0.4653264,-0.4742 0.6796678,-0.6177 0.5039623,-0.33746 1.0157321,-0.5065 1.6217201,-0.53562 0.538326,-0.0256 1.0248294,0.0746 1.5082517,0.31109 0.665074,0.32548 1.1364593,0.79739 1.4616053,1.46319 0.233795,0.47874 0.335401,0.96701 0.311946,1.49906 l -0.01218,0.27678 0.199164,0.13128 c 0.109539,0.0722 0.316714,0.24806 0.460388,0.39074 0.813111,0.8075 1.113884,1.93261 0.816781,3.05541 -0.121193,0.45797 -0.457432,1.0356 -0.805977,1.3845 -0.333016,0.33338 -0.91483,0.67934 -1.34009,0.79684 -0.4502688,0.12445 -0.3942394,0.12289 -4.2354147,0.11981 -1.971615,-0.002 -3.6501125,-0.0125 -3.7299933,-0.0242 z m 7.6451481,-0.82834 c 0.545805,-0.1461 1.0314,-0.47864 1.358863,-0.93055 0.13507,-0.18643 0.302726,-0.55751 0.365299,-0.80853 0.07721,-0.30967 0.07737,-0.79285 3.72e-4,-1.10158 -0.185769,-0.74499 -0.759998,-1.39061 -1.459688,-1.64119 -0.139897,-0.0501 -0.212904,-0.0884 -0.205441,-0.1079 0.230936,-0.60245 0.205529,-1.2656 -0.07086,-1.84969 -0.2420436,-0.51155 -0.6116411,-0.88174 -1.1201832,-1.12201 -0.3646743,-0.1723 -0.5814957,-0.2198 -1.0034057,-0.2198 -0.291794,0 -0.3936692,0.0101 -0.5647214,0.0557 -0.6755653,0.17987 -1.2418071,0.63564 -1.5361388,1.23642 l -0.068336,0.13949 -0.1123702,-0.0569 c -0.2090683,-0.10576 -0.5171066,-0.18751 -0.7629941,-0.20252 -0.8518289,-0.052 -1.6804874,0.51274 -1.9528265,1.33076 -0.1542208,0.46323 -0.1386557,0.90554 0.048962,1.39126 0.012582,0.0324 -0.00809,0.0365 -0.1432276,0.0278 -0.1904101,-0.0122 -0.5000142,0.0466 -0.7431816,0.14113 -0.4999194,0.19422 -0.9533668,0.67125 -1.12307682,1.18155 -0.15734055,0.47307 -0.14715788,0.90357 0.032344,1.36764 0.1940906,0.50172 0.6700544,0.95419 1.1841482,1.12568 0.1034943,0.0344 0.2471302,0.0725 0.3191908,0.0848 0.07415,0.0126 1.6976013,0.0205 3.7403281,0.0181 l 3.6093097,-0.004 z m -5.2655768,-1.2975 C 4.2750097,292.90743 3.844102,292.6375 3.844102,292.62395 c 0,-0.0137 0.4309077,-0.28347 0.9575712,-0.59989 l 0.9575723,-0.5752 0.00674,0.39089 0.00674,0.39094 h 1.5707862 1.5707918 v 0.39326 0.39327 H 7.34349 5.772697 l -0.00674,0.39094 -0.00674,0.39093 z m 2.1483996,-2.17014 v -0.39612 H 5.3786739 3.807272 v -0.39326 -0.39328 h 1.570792 1.5707932 l 0.00674,-0.39093 0.00674,-0.39093 0.9575575,0.57524 c 0.5266596,0.3164 0.9606572,0.58453 0.9644402,0.59591 0.00375,0.0115 -0.4050204,0.26764 -0.908454,0.56956 -0.5034315,0.30187 -0.940193,0.56487 -0.970577,0.58441 l -0.055248,0.0354 z" + id="path826" + inkscape:connector-curvature="0" /> + </g> + </g> +</svg> diff --git a/ui/opensnitch/res/icon.png b/ui/opensnitch/res/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..786e9a0726411270830723c271db5b1425c2307c GIT binary patch literal 7780 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 literal 0 HcmV?d00001 diff --git a/ui/opensnitch/res/preferences.ui b/ui/opensnitch/res/preferences.ui new file mode 100644 index 0000000..6591994 --- /dev/null +++ b/ui/opensnitch/res/preferences.ui @@ -0,0 +1,1406 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PreferencesDialog</class> + <widget class="QDialog" name="PreferencesDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>626</width> + <height>442</height> + </rect> + </property> + <property name="windowTitle"> + <string>Preferences</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QTabWidget" name="tabWidget"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="tabPosition"> + <enum>QTabWidget::North</enum> + </property> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="QWidget" name="tab"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <attribute name="title"> + <string>Pop-ups</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="3"> + <widget class="QCheckBox" name="popupsCheck"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="2" column="0" colspan="4"> + <widget class="QGroupBox" name="groupBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Default options</string> + </property> + <layout class="QGridLayout" name="gridLayout_5" rowstretch="0,0,0,0,0,0,0,0" columnstretch="1,0"> + <item row="7" column="0" colspan="2"> + <layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,0,0"> + <item> + <widget class="QCheckBox" name="uidCheck"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>If checked, this field will be selected when a pop-up is displayed</string> + </property> + <property name="text"> + <string>User ID</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="dstPortCheck"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>If checked, this field will be selected when a pop-up is displayed</string> + </property> + <property name="text"> + <string>Destination port</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="dstIPCheck"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>If checked, this field will be selected when a pop-up is displayed</string> + </property> + <property name="text"> + <string>Destination IP</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="comboUIAction"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <item> + <property name="text"> + <string>deny</string> + </property> + <property name="icon"> + <iconset theme="emblem-important"> + <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset> + </property> + </item> + <item> + <property name="text"> + <string>allow</string> + </property> + <property name="icon"> + <iconset theme="emblem-default"> + <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset> + </property> + </item> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_2"> + <property name="toolTip"> + <string><html><head/><body><p>Pop-up default action.</p><p>When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.</p><p><br/></p><p>While a pop-up is asking the user to allow or deny a connection:</p><p>1. new outgoing connections are denied.</p><p>2. known connections are allowed or denied based on the rules defined by the user.</p></body></html></string> + </property> + <property name="text"> + <string>Action</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QComboBox" name="comboUIDialogPos"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <item> + <property name="text"> + <string>center</string> + </property> + </item> + <item> + <property name="text"> + <string>top right</string> + </property> + </item> + <item> + <property name="text"> + <string>bottom right</string> + </property> + </item> + <item> + <property name="text"> + <string>top left</string> + </property> + </item> + <item> + <property name="text"> + <string>bottom left</string> + </property> + </item> + </widget> + </item> + <item row="1" column="1"> + <widget class="QComboBox" name="comboUIDuration"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <item> + <property name="text"> + <string>once</string> + </property> + </item> + <item> + <property name="text"> + <string>30s</string> + </property> + </item> + <item> + <property name="text"> + <string>5m</string> + </property> + </item> + <item> + <property name="text"> + <string>15m</string> + </property> + </item> + <item> + <property name="text"> + <string>30m</string> + </property> + </item> + <item> + <property name="text"> + <string>1h</string> + </property> + </item> + <item> + <property name="text"> + <string>until reboot</string> + </property> + </item> + <item> + <property name="text"> + <string>forever</string> + </property> + </item> + </widget> + </item> + <item row="6" column="0"> + <widget class="QLabel" name="label_18"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string><html><head/><body><p>By default when a new pop-up appears, in its simplest form, you'll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).</p><p>With these options, you can choose multiple fields to filter connections for.</p></body></html></string> + </property> + <property name="text"> + <string>Filter connections also by:</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QComboBox" name="comboUITarget"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <item> + <property name="text"> + <string>by executable</string> + </property> + </item> + <item> + <property name="text"> + <string>by command line</string> + </property> + </item> + <item> + <property name="text"> + <string>by destination port</string> + </property> + </item> + <item> + <property name="text"> + <string>by destination ip</string> + </property> + </item> + <item> + <property name="text"> + <string>by user id</string> + </property> + </item> + <item> + <property name="text"> + <string>by PID</string> + </property> + </item> + </widget> + </item> + <item row="4" column="0" colspan="2"> + <widget class="Line" name="line_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Default target</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Default position on screen</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_3"> + <property name="toolTip"> + <string>Pop-up default duration</string> + </property> + <property name="text"> + <string>Duration</string> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_17"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>The advanced view allows you to easily select multiple fields to filter connections</string> + </property> + <property name="text"> + <string>Show advanced view by default</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QCheckBox" name="showAdvancedCheck"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string><html><head/><body><p>If checked, the pop-ups will be displayed with the advanced view active.</p></body></html></string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="1" column="3"> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <property name="spacing"> + <number>0</number> + </property> + <item> + <widget class="QPushButton" name="cmdTimeoutUp"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="list-add"> + <normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset> + </property> + <property name="flat"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="spinUITimeout"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="buttonSymbols"> + <enum>QAbstractSpinBox::NoButtons</enum> + </property> + <property name="value"> + <number>30</number> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="cmdTimeoutDown"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="list-remove"> + <normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset> + </property> + <property name="flat"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0" colspan="3"> + <widget class="QLabel" name="label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string><html><head/><body><p>This timeout is the countdown you see when a pop-up dialog is shown.</p><p>If the pop-up is not answered, the default options will be applied.</p></body></html></string> + </property> + <property name="text"> + <string>Default timeout</string> + </property> + </widget> + </item> + <item row="0" column="0" colspan="3"> + <widget class="QLabel" name="label_16"> + <property name="text"> + <string>Disable pop-ups, only display an notification</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_4"> + <attribute name="title"> + <string>UI</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_6"> + <item row="2" column="0"> + <widget class="QCheckBox" name="checkUIRules"> + <property name="toolTip"> + <string><html><head/><body><p>When this option is selected, the rules of the selected duration won't be added to the list of temporary rules in the GUI.</p><p><br/></p><p>Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</p></body></html></string> + </property> + <property name="text"> + <string>Don't save rules of duration</string> + </property> + </widget> + </item> + <item row="3" column="0" colspan="2"> + <widget class="QGroupBox" name="groupNotifs"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Desktop notifications</string> + </property> + <property name="flat"> + <bool>true</bool> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QGridLayout" name="gridLayout_9"> + <item row="0" column="0"> + <widget class="QRadioButton" name="radioSysNotifs"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Use system notifications</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QRadioButton" name="radioQtNotifs"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Use Qt notifications</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QLabel" name="labelNotifsWarning"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_5"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="cmdTestNotifs"> + <property name="text"> + <string>Test</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="comboUITheme"> + <item> + <property name="text"> + <string>System</string> + </property> + </item> + </widget> + </item> + <item row="4" column="0" colspan="2"> + <widget class="QGroupBox" name="groupBox_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Events tab columns</string> + </property> + <layout class="QGridLayout" name="gridLayout_7"> + <item row="0" column="0"> + <widget class="QCheckBox" name="checkHideTime"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Time</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="6" column="0"> + <widget class="QCheckBox" name="checkHideRule"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Rule</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QCheckBox" name="checkHideNode"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Node</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QCheckBox" name="checkHideProto"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Protocol</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QCheckBox" name="checkHideAction"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Action</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QCheckBox" name="checkHideDst"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Destination</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QCheckBox" name="checkHideProc"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Process</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_21"> + <property name="text"> + <string>Theme</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QComboBox" name="comboUIRules"> + <item> + <property name="text"> + <string>any temporary rules</string> + </property> + </item> + <item> + <property name="text"> + <string>once</string> + </property> + </item> + </widget> + </item> + <item row="1" column="0" colspan="2"> + <widget class="QLabel" name="labelThemeError"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_3"> + <attribute name="title"> + <string>Nodes</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_4" rowstretch="0,0,0,0,0,0,0,0,0,0,0"> + <item row="9" column="0"> + <widget class="QLabel" name="label_13"> + <property name="text"> + <string>Process monitor method</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QComboBox" name="comboNodes"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_7"> + <property name="toolTip"> + <string><html><head/><body><p>Log file to write logs.<br/></p><p>/dev/stdout will print logs to the standard output.</p></body></html></string> + </property> + <property name="text"> + <string>Log file</string> + </property> + </widget> + </item> + <item row="8" column="2"> + <widget class="QCheckBox" name="checkInterceptUnknown"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="7" column="0"> + <widget class="QLabel" name="label_11"> + <property name="toolTip"> + <string><html><head/><body><p>The default duration will take place when there's no UI connected.</p></body></html></string> + </property> + <property name="text"> + <string>Default duration</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QCheckBox" name="checkApplyToNodes"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Apply configuration to all nodes</string> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QLabel" name="labelNodeVersion"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + </widget> + </item> + <item row="6" column="0"> + <widget class="QLabel" name="label_10"> + <property name="toolTip"> + <string><html><head/><body><p>The default action will take place when there's no UI connected.</p></body></html></string> + </property> + <property name="text"> + <string>Default action when the GUI is disconnected</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_8"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>HostName</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QLabel" name="labelNodeName"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + </widget> + </item> + <item row="9" column="2"> + <widget class="QComboBox" name="comboNodeMonitorMethod"> + <property name="accessibleDescription"> + <string notr="true"/> + </property> + <item> + <property name="text"> + <string notr="true">proc</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">ebpf</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">audit</string> + </property> + </item> + </widget> + </item> + <item row="7" column="2"> + <widget class="QComboBox" name="comboNodeDuration"> + <item> + <property name="text"> + <string>once</string> + </property> + </item> + <item> + <property name="text"> + <string>until restart</string> + </property> + </item> + <item> + <property name="text"> + <string>always</string> + </property> + </item> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_15"> + <property name="toolTip"> + <string><html><head/><body><p>Address of the node.</p><p>Default: unix:///tmp/osui.sock (unix:// is mandatory if it's a Unix socket)</p><p>It can also be an IP address with the port: 127.0.0.1:50051</p></body></html></string> + </property> + <property name="text"> + <string>Address</string> + </property> + </widget> + </item> + <item row="8" column="0"> + <widget class="QLabel" name="label_12"> + <property name="toolTip"> + <string><html><head/><body><p>If checked, OpenSnitch will prompt you to allow or deny connections that don't have an associated PID, due to several reasons, mostly due to bad state connections.</p><p>The pop-up dialog will only contain information about the network connection.</p><p>There're some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.</p></body></html></string> + </property> + <property name="text"> + <string>Debug invalid connections</string> + </property> + </widget> + </item> + <item row="6" column="2"> + <widget class="QComboBox" name="comboNodeAction"> + <property name="editable"> + <bool>false</bool> + </property> + <item> + <property name="text"> + <string>deny</string> + </property> + <property name="icon"> + <iconset theme="emblem-important"> + <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset> + </property> + </item> + <item> + <property name="text"> + <string>allow</string> + </property> + <property name="icon"> + <iconset theme="emblem-default"> + <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset> + </property> + </item> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_9"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Ignored"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Version</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + </widget> + </item> + <item row="10" column="2"> + <widget class="QComboBox" name="comboNodeLogLevel"> + <property name="accessibleDescription"> + <string notr="true"/> + </property> + <item> + <property name="text"> + <string notr="true">DEBUG</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">INFO</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">IMPORTANT</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">WARNING</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">ERROR</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">FATAL</string> + </property> + </item> + </widget> + </item> + <item row="4" column="2"> + <widget class="QComboBox" name="comboNodeAddress"> + <property name="editable"> + <bool>true</bool> + </property> + <item> + <property name="text"> + <string>unix:///tmp/osui.sock</string> + </property> + </item> + </widget> + </item> + <item row="5" column="2"> + <widget class="QComboBox" name="comboNodeLogFile"> + <property name="editable"> + <bool>true</bool> + </property> + <item> + <property name="text"> + <string>/var/log/opensnitchd.log</string> + </property> + </item> + <item> + <property name="text"> + <string>/dev/stdout</string> + </property> + </item> + </widget> + </item> + <item row="0" column="1"> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Preferred</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="10" column="0"> + <widget class="QLabel" name="label_14"> + <property name="text"> + <string>Default log level</string> + </property> + </widget> + </item> + <item row="3" column="0" colspan="3"> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_2"> + <attribute name="title"> + <string>Database</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_3" rowstretch="0,0,0,0,0,0"> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> + <property name="leftMargin"> + <number>9</number> + </property> + <property name="verticalSpacing"> + <number>15</number> + </property> + <item row="1" column="1" colspan="3"> + <widget class="QLabel" name="dbLabel"> + <property name="text"> + <string/> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="3"> + <widget class="QComboBox" name="comboDBType"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <item> + <property name="text"> + <string>In memory</string> + </property> + </item> + <item> + <property name="text"> + <string>File</string> + </property> + </item> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_6"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Database type</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QPushButton" name="dbFileButton"> + <property name="text"> + <string>Select</string> + </property> + <property name="icon"> + <iconset theme="document-open"> + <normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset> + </property> + </widget> + </item> + <item row="5" column="0"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="1"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="2" column="0" colspan="4"> + <layout class="QGridLayout" name="gridLayout_8"> + <property name="verticalSpacing"> + <number>10</number> + </property> + <item row="0" column="3"> + <widget class="QSpinBox" name="spinDBMaxDays"> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="buttonSymbols"> + <enum>QAbstractSpinBox::NoButtons</enum> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>99999</number> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QPushButton" name="cmdDBMaxDaysUp"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="list-add"> + <normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset> + </property> + <property name="flat"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="5"> + <widget class="QLabel" name="label_20"> + <property name="text"> + <string>minutes</string> + </property> + <property name="margin"> + <number>5</number> + </property> + </widget> + </item> + <item row="1" column="3"> + <widget class="QSpinBox" name="spinDBPurgeInterval"> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="buttonSymbols"> + <enum>QAbstractSpinBox::NoButtons</enum> + </property> + <property name="minimum"> + <number>5</number> + </property> + <property name="maximum"> + <number>1440</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="labelDBPurgeInterval"> + <property name="text"> + <string>Minutes between events purges</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="0" column="1"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="5"> + <widget class="QLabel" name="label_19"> + <property name="text"> + <string>days</string> + </property> + <property name="margin"> + <number>5</number> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QCheckBox" name="checkDBMaxDays"> + <property name="text"> + <string>Maximum days of events to keep</string> + </property> + </widget> + </item> + <item row="0" column="4"> + <widget class="QPushButton" name="cmdDBMaxDaysDown"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="list-remove"> + <normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset> + </property> + <property name="flat"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QPushButton" name="cmdDBPurgesUp"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="list-add"> + <normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset> + </property> + <property name="flat"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="4"> + <widget class="QPushButton" name="cmdDBPurgesDown"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="list-remove"> + <normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset> + </property> + <property name="flat"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </widget> + </item> + <item row="2" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QPushButton" name="helpButton"> + <property name="mouseTracking"> + <bool>true</bool> + </property> + <property name="toolTip"> + <string/> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="help-browser"> + <normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset> + </property> + <property name="flat"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="cancelButton"> + <property name="text"> + <string>Close</string> + </property> + <property name="icon"> + <iconset theme="window-close"> + <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="applyButton"> + <property name="text"> + <string>Apply</string> + </property> + <property name="icon"> + <iconset theme="document-save"> + <normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="acceptButton"> + <property name="text"> + <string>Save</string> + </property> + <property name="icon"> + <iconset theme="emblem-default"> + <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="statusLabel"> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/ui/opensnitch/res/process_details.ui b/ui/opensnitch/res/process_details.ui new file mode 100644 index 0000000..5c34de5 --- /dev/null +++ b/ui/opensnitch/res/process_details.ui @@ -0,0 +1,269 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ProcessDetailsDialog</class> + <widget class="QDialog" name="ProcessDetailsDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>731</width> + <height>478</height> + </rect> + </property> + <property name="windowTitle"> + <string>Process details</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="labelProcIcon"> + <property name="minimumSize"> + <size> + <width>48</width> + <height>48</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>64</width> + <height>64</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QLabel" name="labelProcName"> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="text"> + <string>loading...</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="labelProcArgs"> + <property name="text"> + <string>loading...</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="labelCwd"> + <property name="text"> + <string>CWD: loading...</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QLabel" name="labelStatm"> + <property name="text"> + <string>mem stats: loading...</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QTabWidget" name="tabWidget"> + <property name="tabPosition"> + <enum>QTabWidget::South</enum> + </property> + <property name="currentIndex"> + <number>0</number> + </property> + <property name="documentMode"> + <bool>true</bool> + </property> + <widget class="QWidget" name="tab_2"> + <attribute name="title"> + <string>Status</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="1"> + <widget class="QPlainTextEdit" name="textStatus"> + <property name="undoRedoEnabled"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_3"> + <attribute name="title"> + <string>Open files</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_4"> + <item row="1" column="0"> + <widget class="QPlainTextEdit" name="textOpenedFiles"> + <property name="undoRedoEnabled"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="tabWidgetPage1"> + <attribute name="title"> + <string>I/O Statistics</string> + </attribute> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="0"> + <widget class="QPlainTextEdit" name="textIOStats"> + <property name="undoRedoEnabled"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="tabWidgetPage2"> + <attribute name="title"> + <string>Memory mapped files</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="1" column="0"> + <widget class="QPlainTextEdit" name="textMappedFiles"> + <property name="undoRedoEnabled"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab"> + <attribute name="title"> + <string>Stack</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_5"> + <item row="0" column="0"> + <widget class="QPlainTextEdit" name="textStack"> + <property name="undoRedoEnabled"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_4"> + <attribute name="title"> + <string>Environment variables</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_6"> + <item row="0" column="0"> + <widget class="QPlainTextEdit" name="textEnv"> + <property name="undoRedoEnabled"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Application pids</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboPids"> + <property name="maxVisibleItems"> + <number>100</number> + </property> + <property name="sizeAdjustPolicy"> + <enum>QComboBox::AdjustToContents</enum> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="cmdAction"> + <property name="toolTip"> + <string>Start or stop monitoring this process</string> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="media-playback-start"/> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="cmdClose"> + <property name="text"> + <string>Close</string> + </property> + <property name="icon"> + <iconset theme="window-close"/> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/ui/opensnitch/res/prompt.ui b/ui/opensnitch/res/prompt.ui new file mode 100644 index 0000000..7314292 --- /dev/null +++ b/ui/opensnitch/res/prompt.ui @@ -0,0 +1,866 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Dialog</class> + <widget class="QDialog" name="Dialog"> + <property name="windowModality"> + <enum>Qt::NonModal</enum> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>520</width> + <height>308</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>200</height> + </size> + </property> + <property name="font"> + <font> + <kerning>true</kerning> + </font> + </property> + <property name="windowTitle"> + <string>opensnitch-qt</string> + </property> + <property name="windowIcon"> + <iconset resource="resources.qrc"> + <normaloff>:/pics/icon-white.png</normaloff> + <normalon>:/pics/icon.png</normalon>:/pics/icon-white.png</iconset> + </property> + <property name="sizeGripEnabled"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,0,0,0,0,1"> + <property name="spacing"> + <number>2</number> + </property> + <property name="sizeConstraint"> + <enum>QLayout::SetMinAndMaxSize</enum> + </property> + <property name="leftMargin"> + <number>5</number> + </property> + <property name="topMargin"> + <number>5</number> + </property> + <property name="rightMargin"> + <number>5</number> + </property> + <property name="bottomMargin"> + <number>5</number> + </property> + <item> + <layout class="QFormLayout" name="formLayout"> + <property name="fieldGrowthPolicy"> + <enum>QFormLayout::ExpandingFieldsGrow</enum> + </property> + <property name="labelAlignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + <property name="verticalSpacing"> + <number>0</number> + </property> + <item row="0" column="0"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="iconLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>64</width> + <height>64</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>64</width> + <height>64</height> + </size> + </property> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="resources.qrc">:/pics/icon.png</pixmap> + </property> + <property name="scaledContents"> + <bool>true</bool> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="1"> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="spacing"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="appNameLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <pointsize>16</pointsize> + <weight>75</weight> + <bold>true</bold> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string notr="true">Chromium Web Browser</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="textInteractionFlags"> + <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="appDescriptionLabel"> + <property name="font"> + <font> + <pointsize>10</pointsize> + <weight>50</weight> + <italic>true</italic> + <bold>false</bold> + <underline>true</underline> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string notr="true"><html><head/><body><p>/opt/google/chrome/bin/chrome --something abc --more-long def --for-word-wrapping</p></body></html></string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="appPathLabel"> + <property name="text"> + <string notr="true">(/path/to/bin/chromium)</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="textInteractionFlags"> + <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="argsLabel"> + <property name="font"> + <font> + <pointsize>10</pointsize> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string notr="true">(/path/to/bin/chromium)</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="textInteractionFlags"> + <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item> + <widget class="Line" name="line"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="messageLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>520</width> + <height>40</height> + </size> + </property> + <property name="text"> + <string notr="true">Chromium Web Browser wants to connect to www.evilsocket.net on tcp port 443. And maybe to www.goodsocket.net on port 344</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="margin"> + <number>5</number> + </property> + <property name="textInteractionFlags"> + <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item> + <layout class="QGridLayout" name="gridLayout"> + <property name="topMargin"> + <number>6</number> + </property> + <property name="verticalSpacing"> + <number>3</number> + </property> + <item row="6" column="1"> + <widget class="QLabel" name="label_6"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <family>Ubuntu</family> + <pointsize>10</pointsize> + <weight>75</weight> + <italic>false</italic> + <bold>true</bold> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string>User ID</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="label_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + <weight>75</weight> + <bold>true</bold> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string><html><head/><body><p><span style=" font-weight:600;">Executed from</span></p></body></html></string> + </property> + </widget> + </item> + <item row="6" column="0"> + <widget class="QCheckBox" name="checkUserID"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QCheckBox" name="checkDstIP"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="6" column="3"> + <widget class="QLabel" name="uidLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>90</width> + <height>0</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string>TextLabel</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="1" column="3"> + <widget class="QLabel" name="sourceIPLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>90</width> + <height>0</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string>TextLabel</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <family>Ubuntu</family> + <pointsize>10</pointsize> + <weight>75</weight> + <italic>false</italic> + <bold>true</bold> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string>Source IP</string> + </property> + </widget> + </item> + <item row="7" column="1"> + <widget class="QLabel" name="pidLabelWidget"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <family>Ubuntu</family> + <pointsize>10</pointsize> + <weight>75</weight> + <italic>false</italic> + <bold>true</bold> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string>Process ID</string> + </property> + </widget> + </item> + <item row="5" column="3"> + <widget class="QLabel" name="destPortLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>90</width> + <height>0</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string>TextLabel</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="2" column="3"> + <widget class="QLabel" name="destIPLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>90</width> + <height>0</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string>TextLabel</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QComboBox" name="whatIPCombo"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumContentsLength"> + <number>0</number> + </property> + </widget> + </item> + <item row="0" column="3"> + <widget class="QLabel" name="cwdLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>90</width> + <height>0</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string/> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + <property name="textInteractionFlags"> + <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QCheckBox" name="checkDstPort"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="label_3"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <family>Ubuntu</family> + <pointsize>10</pointsize> + <weight>75</weight> + <italic>false</italic> + <bold>true</bold> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string>Destination IP</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QLabel" name="destPortLabel_1"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <family>Ubuntu</family> + <pointsize>10</pointsize> + <weight>75</weight> + <italic>false</italic> + <bold>true</bold> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string>Dst Port</string> + </property> + </widget> + </item> + <item row="7" column="3"> + <widget class="QLabel" name="pidLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>90</width> + <height>0</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string>TextLabel</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="label_5"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string notr="true"/> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout" stretch="3,2,0,0,0"> + <property name="sizeConstraint"> + <enum>QLayout::SetMinimumSize</enum> + </property> + <item> + <widget class="QComboBox" name="whatCombo"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>97</width> + <height>26</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>204</width> + <height>30</height> + </size> + </property> + <item> + <property name="text"> + <string>from this executable</string> + </property> + </item> + <item> + <property name="text"> + <string>from this command line</string> + </property> + </item> + <item> + <property name="text"> + <string>this destination port</string> + </property> + </item> + <item> + <property name="text"> + <string>this user</string> + </property> + </item> + <item> + <property name="text"> + <string>this destination ip</string> + </property> + </item> + <item> + <property name="text"> + <string>from this PID</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QComboBox" name="durationCombo"> + <property name="minimumSize"> + <size> + <width>97</width> + <height>26</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>204</width> + <height>30</height> + </size> + </property> + <property name="currentText"> + <string>once</string> + </property> + <item> + <property name="text"> + <string>once</string> + </property> + </item> + <item> + <property name="text"> + <string>30s</string> + </property> + </item> + <item> + <property name="text"> + <string>5m</string> + </property> + </item> + <item> + <property name="text"> + <string>15m</string> + </property> + </item> + <item> + <property name="text"> + <string>30m</string> + </property> + </item> + <item> + <property name="text"> + <string>1h</string> + </property> + </item> + <item> + <property name="text"> + <string>until reboot</string> + </property> + </item> + <item> + <property name="text"> + <string>forever</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QPushButton" name="denyButton"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>80</width> + <height>26</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>30</height> + </size> + </property> + <property name="text"> + <string>Deny</string> + </property> + <property name="icon"> + <iconset theme="emblem-important"> + <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="applyButton"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>80</width> + <height>26</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>204</width> + <height>30</height> + </size> + </property> + <property name="text"> + <string>Allow</string> + </property> + <property name="icon"> + <iconset theme="emblem-default"> + <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="checkAdvanced"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>24</width> + <height>24</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>40</width> + <height>30</height> + </size> + </property> + <property name="text"> + <string>+</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources> + <include location="resources.qrc"/> + <include location="../../../../../../../../.designer/backup/resources.qrc"/> + </resources> + <connections/> +</ui> diff --git a/ui/opensnitch/res/resources.qrc b/ui/opensnitch/res/resources.qrc new file mode 100644 index 0000000..35bde88 --- /dev/null +++ b/ui/opensnitch/res/resources.qrc @@ -0,0 +1,8 @@ +<RCC> + <qresource prefix="pics"> + <file>icon-white.svg</file> + <file>icon-white.png</file> + <file>icon-red.png</file> + <file>icon.png</file> + </qresource> +</RCC> diff --git a/ui/opensnitch/res/ruleseditor.ui b/ui/opensnitch/res/ruleseditor.ui new file mode 100644 index 0000000..2a14086 --- /dev/null +++ b/ui/opensnitch/res/ruleseditor.ui @@ -0,0 +1,946 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>RulesDialog</class> + <widget class="QDialog" name="RulesDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>552</width> + <height>491</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="windowTitle"> + <string>Rule</string> + </property> + <layout class="QGridLayout" name="gridLayout_4"> + <item row="5" column="1"> + <widget class="QGroupBox" name="enableGroup"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="flat"> + <bool>false</bool> + </property> + <property name="checkable"> + <bool>false</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QGridLayout" name="gridLayout" columnstretch="0,0,0,0,0,0,0"> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> + <property name="topMargin"> + <number>4</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <property name="verticalSpacing"> + <number>12</number> + </property> + <item row="3" column="1"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Action</string> + </property> + </widget> + </item> + <item row="4" column="4"> + <spacer name="horizontalSpacer_5"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="3" column="4"> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="4" column="1"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Duration</string> + </property> + </widget> + </item> + <item row="4" column="6"> + <widget class="QComboBox" name="durationCombo"> + <item> + <property name="text"> + <string>once</string> + </property> + </item> + <item> + <property name="text"> + <string>30s</string> + </property> + </item> + <item> + <property name="text"> + <string>5m</string> + </property> + </item> + <item> + <property name="text"> + <string>15m</string> + </property> + </item> + <item> + <property name="text"> + <string>30m</string> + </property> + </item> + <item> + <property name="text"> + <string>1h</string> + </property> + </item> + <item> + <property name="text"> + <string>until reboot</string> + </property> + </item> + <item> + <property name="text"> + <string>always</string> + </property> + </item> + </widget> + </item> + <item row="3" column="6"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QRadioButton" name="actionDenyRadio"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Deny will just discard the connection</string> + </property> + <property name="text"> + <string>Deny</string> + </property> + <property name="icon"> + <iconset theme="emblem-important"> + <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="actionRejectRadio"> + <property name="toolTip"> + <string>Reject will drop the connection, and kill the socket that initiated it</string> + </property> + <property name="text"> + <string>Reject</string> + </property> + <property name="icon"> + <iconset theme="window-close"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="actionAllowRadio"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Allow will allow the connection</string> + </property> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + <property name="text"> + <string>Allow</string> + </property> + <property name="icon"> + <iconset theme="emblem-default"> + <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item row="6" column="1"> + <widget class="QTabWidget" name="tabWidget"> + <property name="currentIndex"> + <number>0</number> + </property> + <property name="elideMode"> + <enum>Qt::ElideRight</enum> + </property> + <property name="documentMode"> + <bool>true</bool> + </property> + <widget class="QWidget" name="tabWidgetPage1"> + <attribute name="icon"> + <iconset theme="system-run"> + <normaloff>.</normaloff>.</iconset> + </attribute> + <attribute name="title"> + <string>Applications</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="1" colspan="2"> + <widget class="QLineEdit" name="procLine"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="toolTip"> + <string><html><head/><body><p>The value of this field is always the absolute path to the executable: /path/to/binary<br/></p><p>Examples:</p><p>- Simple: /path/to/binary</p><p>- Multiple paths: ^/usr/lib(64|)/firefox/firefox$</p><p>- Multiple binaries: ^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ </p><p>- Deny/Allow executions from /tmp:</p><p>^/(var/|)tmp/.*$<br/></p><p>For more examples visit the <a href="https://github.com/evilsocket/opensnitch/wiki/Rules-examples">wiki page</a> or ask on the <a href="https://github.com/evilsocket/opensnitch/discussions">Discussion forums</a>.</p></body></html></string> + </property> + <property name="placeholderText"> + <string notr="true">/path/to/executable, .*/bin/executable[0-9\.]+$, ...</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QCheckBox" name="checkCmdlineRegexp"> + <property name="text"> + <string>Is regular expression</string> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QCheckBox" name="uidCheck"> + <property name="text"> + <string>From this user ID</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QCheckBox" name="cmdlineCheck"> + <property name="text"> + <string>From this command line</string> + </property> + </widget> + </item> + <item row="2" column="1" colspan="2"> + <widget class="QLineEdit" name="cmdlineLine"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="toolTip"> + <string><html><head/><body><p>This field will contain and match the command line that was executed by the user.<br/></p><p>If the user typed the command, only the command will appear:</p><p>telnet 1.2.3.4<br/></p><p>If the user typed the absolute or relative path to the command, that is what will appear:</p><p>/usr/bin/telnet 1.2.3.4</p><p>../../../usr/bin/telnet 1.2.3.4</p></body></html></string> + </property> + <property name="placeholderText"> + <string notr="true">curl -L https://www.domain.com</string> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QCheckBox" name="pidCheck"> + <property name="text"> + <string>From this PID</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_11"> + <item> + <spacer name="horizontalSpacer_11"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLineEdit" name="pidLine"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </item> + <item row="4" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_8"> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLineEdit" name="uidLine"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="0"> + <widget class="QCheckBox" name="procCheck"> + <property name="text"> + <string>From this executable</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QCheckBox" name="checkProcRegexp"> + <property name="text"> + <string>is regular expression</string> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="tabWidgetPage2"> + <attribute name="icon"> + <iconset theme="preferences-system-network"> + <normaloff>.</normaloff>.</iconset> + </attribute> + <attribute name="title"> + <string>Network</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="0" colspan="2"> + <widget class="QCheckBox" name="dstPortCheck"> + <property name="text"> + <string>To this port</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QCheckBox" name="dstHostCheck"> + <property name="text"> + <string>To this host</string> + </property> + </widget> + </item> + <item row="0" column="2" colspan="3"> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>133</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="5"> + <widget class="QComboBox" name="protoCombo"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="toolTip"> + <string><html><head/><body><p>Only TCP, UDP or UDPLITE are allowed</p><p>You can use regexp, i.e.: ^(TCP|UDP)$</p></body></html></string> + </property> + <property name="editable"> + <bool>true</bool> + </property> + <property name="currentText"> + <string>TCP</string> + </property> + <item> + <property name="text"> + <string>TCP</string> + </property> + </item> + <item> + <property name="text"> + <string>UDP</string> + </property> + </item> + <item> + <property name="text"> + <string>UDPLITE</string> + </property> + </item> + <item> + <property name="text"> + <string>TCP6</string> + </property> + </item> + <item> + <property name="text"> + <string>UDP6</string> + </property> + </item> + <item> + <property name="text"> + <string>UDPLITE6</string> + </property> + </item> + </widget> + </item> + <item row="0" column="5"> + <widget class="QLineEdit" name="dstPortLine"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="toolTip"> + <string><html><head/><body><p>You can specify multiple ports using regular expressions:</p><p><br/></p><p>- 53, 80 or 443:</p><p>^(53|80|443)$</p><p><br/></p><p>- 53, 443 or 5551, 5552, 5553, etc:</p><p>^(53|443|555[0-9])$</p></body></html></string> + </property> + </widget> + </item> + <item row="1" column="0" colspan="4"> + <widget class="QCheckBox" name="protoCheck"> + <property name="text"> + <string>Protocol</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QCheckBox" name="dstIPCheck"> + <property name="text"> + <string>To this IP / Network</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <spacer name="horizontalSpacer_9"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="2" column="1"> + <spacer name="horizontalSpacer_10"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="2" column="2" colspan="4"> + <widget class="QLineEdit" name="dstHostLine"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="toolTip"> + <string>Commas or spaces are not allowed to specify multiple domains. + +Use regular expressions instead: +.*(opensnitch|duckduckgo).com +.*\.google.com + +or a single domain: +www.gnu.org - it'll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ... +gnu.org - it'll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</string> + </property> + <property name="placeholderText"> + <string>www.domain.org, .*\.domain.org</string> + </property> + </widget> + </item> + <item row="3" column="2" colspan="4"> + <widget class="QComboBox" name="dstIPCombo"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="toolTip"> + <string>You can specify a single IP: +- 192.168.1.1 + +or a regular expression: +- 192\.168\.1\.[0-9]+ + +multiple IPs: +- ^(192\.168\.1\.1|172\.16\.0\.1)$ + +You can also specify a subnet: +- 192.168.1.0/24 + +Note: Commas or spaces are not allowed to separate IPs or networks.</string> + </property> + <property name="editable"> + <bool>true</bool> + </property> + <item> + <property name="text"> + <string>LAN</string> + </property> + </item> + <item> + <property name="text"> + <string>127.0.0.0/8</string> + </property> + </item> + <item> + <property name="text"> + <string>192.168.0.0/24</string> + </property> + </item> + <item> + <property name="text"> + <string>192.168.1.0/24</string> + </property> + </item> + <item> + <property name="text"> + <string>192.168.2.0/24</string> + </property> + </item> + <item> + <property name="text"> + <string>192.168.0.0/16</string> + </property> + </item> + <item> + <property name="text"> + <string>169.254.0.0/16</string> + </property> + </item> + <item> + <property name="text"> + <string>172.16.0.0/12</string> + </property> + </item> + <item> + <property name="text"> + <string>10.0.0.0/8</string> + </property> + </item> + <item> + <property name="text"> + <string>::1/128</string> + </property> + </item> + <item> + <property name="text"> + <string>fc00::/7</string> + </property> + </item> + <item> + <property name="text"> + <string>ff00::/8</string> + </property> + </item> + <item> + <property name="text"> + <string>fe80::/10</string> + </property> + </item> + <item> + <property name="text"> + <string>fd00::/8</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="tabWidgetPage3"> + <attribute name="icon"> + <iconset theme="document-properties"> + <normaloff>.</normaloff>.</iconset> + </attribute> + <attribute name="title"> + <string>List of domains/IPs</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_5"> + <item row="3" column="0"> + <widget class="QCheckBox" name="dstListNetsCheck"> + <property name="text"> + <string>To this list of network ranges</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QCheckBox" name="dstListIPsCheck"> + <property name="text"> + <string>To this list of IPs</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_7"> + <item> + <widget class="QPushButton" name="selectIPsListButton"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="document-open"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="dstListIPsLine"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="toolTip"> + <string><html><head/><body><p>Select a directory with files containing list of IPs to block or allow:</p><p>1.2.3.4.5</p><p>1.2.3.4.6</p><p>.</p><p>etc.</p><p>One IP per line. Empty lines or started with # are ignored.</p></body></html></string> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="0"> + <widget class="QCheckBox" name="dstListsCheck"> + <property name="text"> + <string>To this list of domains</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_9"> + <item> + <widget class="QPushButton" name="selectNetsListButton"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="document-open"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="dstListNetsLine"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="toolTip"> + <string><html><head/><body><p>Select a directory with files containing list of network ranges to block or allow:</p><p>1.2.3.0/24</p><p>80.34.56.0/20</p><p>.</p><p>etc.<br/></p><p>One Network Range per line. Empty lines or started with # are ignored.</p></body></html></string> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <item> + <widget class="QPushButton" name="selectListButton"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="document-open"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="dstListsLine"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="toolTip"> + <string><html><head/><body><p>Select a directory with lists of domains to block or allow.</p><p>Put inside that directory files with any extension containing lists of domains.</p><p><br/>The format of each entry of a list is as follow (hosts format):</p><p>127.0.0.1 www.domain.com</p><p>or </p><p>0.0.0.0 www.domain.com</p><p>Empty lines or started with # are ignored.</p></body></html></string> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0"> + <widget class="QCheckBox" name="dstListRegexpCheck"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>To this list of domains +(regular expressions)</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_10"> + <item> + <widget class="QPushButton" name="selectListRegexpButton"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="document-open"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="dstRegexpListsLine"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="toolTip"> + <string><html><head/><body><p>Select a directory with files containing regular expressions of domains to block or allow:</p><p>.*\.example\.com</p><p>You can also use a domain as is: &quot;example.com&quot; , and it'll match whatever.example.com, whatever.example.com.localdomain, etc.</p><p>One domain per line. Empty lines or started with # are ignored.</p></body></html></string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab"> + <attribute name="title"> + <string>More</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_6"> + <item row="0" column="0"> + <widget class="QCheckBox" name="sensitiveCheck"> + <property name="toolTip"> + <string><html><head/><body><p>By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.<br/></p><p>If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.</p></body></html></string> + </property> + <property name="text"> + <string>Case-sensitive</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Maximum</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </widget> + </item> + <item row="9" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <spacer name="horizontalSpacer_6"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Apply|QDialogButtonBox::Close|QDialogButtonBox::Help|QDialogButtonBox::Reset</set> + </property> + </widget> + </item> + </layout> + </item> + <item row="8" column="1"> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="7" column="1"> + <widget class="QLabel" name="statusLabel"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="text"> + <string/> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Node</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="nodesCombo"/> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QCheckBox" name="nodeApplyAllCheck"> + <property name="text"> + <string>Apply rule to all nodes</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="1"> + <widget class="QCheckBox" name="enableCheck"> + <property name="text"> + <string>Enable</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QCheckBox" name="precedenceCheck"> + <property name="toolTip"> + <string>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one. + +You must name the rule in such manner that it'll be checked first, because they're checked in alphabetical order. For example: + +[x] Priority - 000-priority-rule +[ ] Priority - 001-less-priority-rule</string> + </property> + <property name="text"> + <string>Priority rule</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Name</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="ruleNameEdit"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them. + +000-allow-localhost +001-deny-broadcast +...</string> + </property> + <property name="placeholderText"> + <string>leave blank to autocreate</string> + </property> + <property name="clearButtonEnabled"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/ui/opensnitch/res/stats.ui b/ui/opensnitch/res/stats.ui new file mode 100644 index 0000000..0c979b6 --- /dev/null +++ b/ui/opensnitch/res/stats.ui @@ -0,0 +1,1652 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>StatsDialog</class> + <widget class="QDialog" name="StatsDialog"> + <property name="windowModality"> + <enum>Qt::NonModal</enum> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>863</width> + <height>600</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>300</width> + <height>220</height> + </size> + </property> + <property name="font"> + <font> + <kerning>true</kerning> + </font> + </property> + <property name="windowTitle"> + <string>OpenSnitch Network Statistics</string> + </property> + <property name="windowIcon"> + <iconset resource="resources.qrc"> + <normaloff>:/pics/icon-white.svg</normaloff> + <normalon>:/pics/icon-white.svg</normalon> + <disabledoff>:/pics/icon-white.svg</disabledoff> + <disabledon>:/pics/icon-white.svg</disabledon> + <activeoff>:/pics/icon-white.svg</activeoff> + <activeon>:/pics/icon-white.svg</activeon>:/pics/icon-white.svg</iconset> + </property> + <property name="sizeGripEnabled"> + <bool>false</bool> + </property> + <property name="modal"> + <bool>false</bool> + </property> + <layout class="QGridLayout" name="gridLayout_4"> + <property name="leftMargin"> + <number>4</number> + </property> + <property name="topMargin"> + <number>2</number> + </property> + <property name="rightMargin"> + <number>4</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <property name="horizontalSpacing"> + <number>2</number> + </property> + <property name="verticalSpacing"> + <number>4</number> + </property> + <item row="3" column="0"> + <widget class="QFrame" name="navToolBar"> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_17"> + <property name="leftMargin"> + <number>4</number> + </property> + <property name="topMargin"> + <number>2</number> + </property> + <property name="rightMargin"> + <number>4</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <item> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Filter</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboAction"> + <item> + <property name="text"> + <string>-</string> + </property> + </item> + <item> + <property name="text"> + <string>Allow</string> + </property> + <property name="icon"> + <iconset theme="emblem-default"> + <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset> + </property> + </item> + <item> + <property name="text"> + <string>Deny</string> + </property> + <property name="icon"> + <iconset theme="emblem-important"> + <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset> + </property> + </item> + <item> + <property name="text"> + <string>Reject</string> + </property> + <property name="icon"> + <iconset theme="window-close"> + <normaloff>.</normaloff>.</iconset> + </property> + </item> + </widget> + </item> + <item> + <widget class="QLineEdit" name="filterLine"> + <property name="text"> + <string notr="true"/> + </property> + <property name="frame"> + <bool>true</bool> + </property> + <property name="placeholderText"> + <string>Ex.: firefox</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Minimum</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="prevButton"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="go-previous"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="labelRowsCount"> + <property name="text"> + <string>0</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="nextButton"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="go-next"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="limitCombo"> + <property name="currentIndex"> + <number>0</number> + </property> + <item> + <property name="text"> + <string>50</string> + </property> + </item> + <item> + <property name="text"> + <string>100</string> + </property> + </item> + <item> + <property name="text"> + <string>200</string> + </property> + </item> + <item> + <property name="text"> + <string>300</string> + </property> + </item> + <item> + <property name="text"> + <string/> + </property> + </item> + </widget> + </item> + <item> + <widget class="QPushButton" name="cmdCleanSql"> + <property name="toolTip"> + <string>Delete all intercepted events</string> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="edit-clear-all"> + <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset> + </property> + <property name="flat"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="helpButton"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="help-browser"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="0" column="0"> + <widget class="QFrame" name="frame"> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <layout class="QGridLayout" name="gridLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <property name="spacing"> + <number>0</number> + </property> + <item row="0" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout_10"> + <item> + <widget class="QPushButton" name="saveButton"> + <property name="toolTip"> + <string>Save to CSV.</string> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="document-save"> + <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset> + </property> + <property name="shortcut"> + <string>Ctrl+S</string> + </property> + <property name="flat"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="prefsButton"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="preferences-system"> + <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset> + </property> + <property name="flat"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="newRuleButton"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>32</horstretch> + <verstretch>32</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Create a new rule</string> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="document-new"> + <normaloff>../../../../../../../../.designer/backup</normaloff>../../../../../../../../.designer/backup</iconset> + </property> + <property name="flat"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_9"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>60</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="nodeLabel"> + <property name="text"> + <string><html><head/><body><p><span style=" font-size:11pt; font-weight:600;">hostname - 192.168.1.1</span></p></body></html></string> + </property> + <property name="margin"> + <number>5</number> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <family>Ubuntu</family> + <pointsize>11</pointsize> + <weight>75</weight> + <italic>false</italic> + <bold>true</bold> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string>Status</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + <property name="margin"> + <number>5</number> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="statusLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <pointsize>11</pointsize> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string>-</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + <property name="margin"> + <number>5</number> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="startButton"> + <property name="toolTip"> + <string>Start or Stop interception</string> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="media-playback-start"> + <normaloff>../../../../../../../../.designer/backup</normaloff>../../../../../../../../.designer/backup</iconset> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <property name="flat"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item row="1" column="0"> + <widget class="QTabWidget" name="tabWidget"> + <property name="tabPosition"> + <enum>QTabWidget::North</enum> + </property> + <property name="tabShape"> + <enum>QTabWidget::Rounded</enum> + </property> + <property name="currentIndex"> + <number>0</number> + </property> + <property name="documentMode"> + <bool>true</bool> + </property> + <widget class="QWidget" name="tab"> + <attribute name="icon"> + <iconset theme="view-sort-ascending"> + <normaloff>../../../../../../../../.designer/backup</normaloff>../../../../../../../../.designer/backup</iconset> + </attribute> + <attribute name="title"> + <string>Events</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_8"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>4</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_16"> + <property name="spacing"> + <number>0</number> + </property> + <item> + <widget class="GenericTableView" name="eventsTable"> + <property name="styleSheet"> + <string notr="true"/> + </property> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Plain</enum> + </property> + <property name="autoScroll"> + <bool>false</bool> + </property> + <property name="editTriggers"> + <set>QAbstractItemView::NoEditTriggers</set> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + <property name="horizontalScrollMode"> + <enum>QAbstractItemView::ScrollPerPixel</enum> + </property> + <property name="showGrid"> + <bool>false</bool> + </property> + <attribute name="verticalHeaderVisible"> + <bool>false</bool> + </attribute> + </widget> + </item> + <item> + <widget class="QScrollBar" name="connectionsTableScrollBar"> + <property name="styleSheet"> + <string notr="true"/> + </property> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_8"> + <attribute name="icon"> + <iconset theme="network-workgroup"> + <normaloff>../../../../../../../../.designer/backup</normaloff>../../../../../../../../.designer/backup</iconset> + </attribute> + <attribute name="title"> + <string>Nodes</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>9</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_12"> + <item> + <widget class="QPushButton" name="cmdNodesBack"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="go-previous"> + <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="nodesLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="spacing"> + <number>0</number> + </property> + <item> + <widget class="GenericTableView" name="nodesTable"> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Plain</enum> + </property> + <property name="autoScroll"> + <bool>false</bool> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + <property name="showGrid"> + <bool>false</bool> + </property> + <property name="sortingEnabled"> + <bool>true</bool> + </property> + <attribute name="horizontalHeaderStretchLastSection"> + <bool>true</bool> + </attribute> + <attribute name="verticalHeaderVisible"> + <bool>false</bool> + </attribute> + <attribute name="verticalHeaderStretchLastSection"> + <bool>false</bool> + </attribute> + </widget> + </item> + <item> + <widget class="QScrollBar" name="verticalScrollBar"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_3"> + <attribute name="icon"> + <iconset theme="address-book-new"> + <normaloff>../../../../../../../../.designer/backup</normaloff>../../../../../../../../.designer/backup</iconset> + </attribute> + <attribute name="title"> + <string>Rules</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="2" column="0"> + <widget class="QSplitter" name="rulesSplitter"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <widget class="QTreeWidget" name="rulesTreePanel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <attribute name="headerVisible"> + <bool>false</bool> + </attribute> + <column> + <property name="text"> + <string notr="true">1</string> + </property> + </column> + <item> + <property name="text"> + <string>Application rules</string> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="icon"> + <iconset theme="system-run"> + <normaloff>../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</normaloff>../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</iconset> + </property> + <item> + <property name="text"> + <string>Permanent</string> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="icon"> + <iconset theme="security-medium"> + <normaloff>../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</normaloff>../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</iconset> + </property> + </item> + <item> + <property name="text"> + <string>Temporary</string> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="icon"> + <iconset theme="edit-clear"> + <normaloff>../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</normaloff>../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</iconset> + </property> + </item> + </item> + <item> + <property name="text"> + <string>Nodes</string> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="icon"> + <iconset theme="system"> + <normaloff>../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</normaloff>../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</iconset> + </property> + </item> + </widget> + <widget class="QWidget" name="horizontalLayoutWidget"> + <layout class="QHBoxLayout" name="horizontalLayout_19"> + <property name="spacing"> + <number>0</number> + </property> + <item> + <widget class="GenericTableView" name="rulesTable"> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + <property name="showGrid"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QScrollBar" name="rulesScrollBar"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + <item row="1" column="0"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_8"> + <item> + <widget class="QComboBox" name="comboRulesFilter"> + <item> + <property name="text"> + <string>All applications</string> + </property> + <property name="icon"> + <iconset theme="system-run"> + <normaloff>.</normaloff>.</iconset> + </property> + </item> + <item> + <property name="text"> + <string>Permanent</string> + </property> + <property name="icon"> + <iconset theme="security-medium"> + <normaloff>.</normaloff>.</iconset> + </property> + </item> + <item> + <property name="text"> + <string>Temporary</string> + </property> + <property name="icon"> + <iconset theme="edit-clear"> + <normaloff>.</normaloff>.</iconset> + </property> + </item> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_11"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </item> + <item row="0" column="0"> + <layout class="QHBoxLayout" name="rulesToolbarLayout"> + <item> + <widget class="QPushButton" name="cmdRulesBack"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="go-previous"> + <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="enableRuleCheck"> + <property name="text"> + <string>enable</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="editRuleButton"> + <property name="toolTip"> + <string>Edit rule</string> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="accessories-text-editor"> + <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="delRuleButton"> + <property name="toolTip"> + <string>Delete rule</string> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="edit-delete"> + <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="nodeRuleLabel"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="ruleLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_4"> + <attribute name="icon"> + <iconset theme="computer"> + <normaloff>../../../../../../../../.designer/backup</normaloff>../../../../../../../../.designer/backup</iconset> + </attribute> + <attribute name="title"> + <string>Hosts</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QPushButton" name="cmdHostsBack"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="go-previous"> + <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="hostsLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_7"> + <property name="spacing"> + <number>0</number> + </property> + <item> + <widget class="GenericTableView" name="hostsTable"> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + <property name="showGrid"> + <bool>false</bool> + </property> + <property name="sortingEnabled"> + <bool>false</bool> + </property> + <property name="cornerButtonEnabled"> + <bool>false</bool> + </property> + <attribute name="horizontalHeaderStretchLastSection"> + <bool>true</bool> + </attribute> + <attribute name="verticalHeaderStretchLastSection"> + <bool>false</bool> + </attribute> + </widget> + </item> + <item> + <widget class="QScrollBar" name="hostsScrollBar"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_7"> + <attribute name="icon"> + <iconset theme="system-run"> + <normaloff>../../../../../../../../.designer/backup</normaloff>../../../../../../../../.designer/backup</iconset> + </attribute> + <attribute name="title"> + <string>Applications</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QPushButton" name="cmdProcsBack"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="go-previous"> + <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="cmdProcDetails"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="system-search"> + <normaloff>../../../../../../../../.designer/backup</normaloff>../../../../../../../../.designer/backup</iconset> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="procsLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_11"> + <property name="spacing"> + <number>0</number> + </property> + <item> + <widget class="GenericTableView" name="procsTable"> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Plain</enum> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + <property name="showGrid"> + <bool>false</bool> + </property> + <property name="sortingEnabled"> + <bool>false</bool> + </property> + <property name="cornerButtonEnabled"> + <bool>true</bool> + </property> + <attribute name="horizontalHeaderStretchLastSection"> + <bool>true</bool> + </attribute> + <attribute name="verticalHeaderStretchLastSection"> + <bool>false</bool> + </attribute> + </widget> + </item> + <item> + <widget class="QScrollBar" name="procsScrollBar"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_2"> + <attribute name="icon"> + <iconset theme="emblem-web"> + <normaloff>../../../../../../../../.designer/backup</normaloff>../../../../../../../../.designer/backup</iconset> + </attribute> + <attribute name="title"> + <string>Addresses</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <widget class="QPushButton" name="cmdAddrsBack"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="go-previous"> + <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="addrsLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_13"> + <property name="spacing"> + <number>0</number> + </property> + <item> + <widget class="GenericTableView" name="addrTable"> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + <property name="showGrid"> + <bool>false</bool> + </property> + <property name="sortingEnabled"> + <bool>false</bool> + </property> + <property name="cornerButtonEnabled"> + <bool>false</bool> + </property> + <attribute name="horizontalHeaderStretchLastSection"> + <bool>true</bool> + </attribute> + <attribute name="verticalHeaderStretchLastSection"> + <bool>false</bool> + </attribute> + </widget> + </item> + <item> + <widget class="QScrollBar" name="addrsScrollBar"> + <property name="maximum"> + <number>50</number> + </property> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_5"> + <attribute name="icon"> + <iconset theme="network-wired"> + <normaloff>../../../../../../../../.designer/backup</normaloff>../../../../../../../../.designer/backup</iconset> + </attribute> + <attribute name="title"> + <string>Ports</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_6"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_5"> + <item> + <widget class="QPushButton" name="cmdPortsBack"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="go-previous"> + <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="portsLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_14"> + <property name="spacing"> + <number>0</number> + </property> + <item> + <widget class="GenericTableView" name="portsTable"> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + <property name="showGrid"> + <bool>false</bool> + </property> + <property name="sortingEnabled"> + <bool>false</bool> + </property> + <property name="cornerButtonEnabled"> + <bool>false</bool> + </property> + <attribute name="horizontalHeaderStretchLastSection"> + <bool>true</bool> + </attribute> + <attribute name="verticalHeaderStretchLastSection"> + <bool>false</bool> + </attribute> + </widget> + </item> + <item> + <widget class="QScrollBar" name="portsScrollBar"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_6"> + <attribute name="icon"> + <iconset theme="system-users"> + <normaloff>../../../../../../../../.designer/backup</normaloff>../../../../../../../../.designer/backup</iconset> + </attribute> + <attribute name="title"> + <string>Users</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_7"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <item> + <widget class="QPushButton" name="cmdUsersBack"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="go-previous"> + <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="usersLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_15"> + <property name="spacing"> + <number>0</number> + </property> + <item> + <widget class="GenericTableView" name="usersTable"> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + <property name="showGrid"> + <bool>false</bool> + </property> + <property name="sortingEnabled"> + <bool>false</bool> + </property> + <property name="cornerButtonEnabled"> + <bool>false</bool> + </property> + <attribute name="horizontalHeaderStretchLastSection"> + <bool>true</bool> + </attribute> + <attribute name="verticalHeaderStretchLastSection"> + <bool>false</bool> + </attribute> + </widget> + </item> + <item> + <widget class="QScrollBar" name="usersScrollBar"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </widget> + </item> + <item row="4" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout_9"> + <property name="spacing"> + <number>6</number> + </property> + <item> + <layout class="QHBoxLayout" name="statsLayout"> + <item> + <widget class="QLabel" name="label_5"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <family>DejaVu Sans</family> + <pointsize>8</pointsize> + <weight>75</weight> + <italic>false</italic> + <bold>true</bold> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string>Connections</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="margin"> + <number>5</number> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="consLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <family>DejaVu Sans</family> + <pointsize>8</pointsize> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string>-</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="margin"> + <number>5</number> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_7"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <family>DejaVu Sans</family> + <pointsize>8</pointsize> + <weight>75</weight> + <italic>false</italic> + <bold>true</bold> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string>Dropped</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + <property name="margin"> + <number>5</number> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="droppedLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <family>DejaVu Sans</family> + <pointsize>8</pointsize> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string>-</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="margin"> + <number>5</number> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <family>DejaVu Sans</family> + <pointsize>8</pointsize> + <weight>75</weight> + <italic>false</italic> + <bold>true</bold> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string>Uptime</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="margin"> + <number>5</number> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="uptimeLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <family>DejaVu Sans</family> + <pointsize>8</pointsize> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string>-</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="margin"> + <number>5</number> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_3"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <family>DejaVu Sans</family> + <pointsize>8</pointsize> + <weight>75</weight> + <italic>false</italic> + <bold>true</bold> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string>Rules</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="margin"> + <number>5</number> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="rulesLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <family>DejaVu Sans</family> + <pointsize>8</pointsize> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string>-</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="margin"> + <number>5</number> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="horizontalSpacer_10"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label_6"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <family>DejaVu Sans</family> + <pointsize>8</pointsize> + <weight>75</weight> + <italic>false</italic> + <bold>true</bold> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string>Version</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + <property name="margin"> + <number>5</number> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="daemonVerLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <family>DejaVu Sans</family> + <pointsize>8</pointsize> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string>-</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="margin"> + <number>5</number> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>GenericTableView</class> + <extends>QTableView</extends> + <header>customwidgets.generictableview</header> + </customwidget> + </customwidgets> + <resources> + <include location="resources.qrc"/> + </resources> + <connections/> +</ui> diff --git a/ui/opensnitch/service.py b/ui/opensnitch/service.py new file mode 100644 index 0000000..802e226 --- /dev/null +++ b/ui/opensnitch/service.py @@ -0,0 +1,738 @@ +from PyQt5 import QtWidgets, QtGui, QtCore + +from datetime import datetime, timedelta +from threading import Thread, Lock, Event +import grpc +import os +import sys +import json + +path = os.path.abspath(os.path.dirname(__file__)) +sys.path.append(path) + +from opensnitch import ui_pb2 +from opensnitch import ui_pb2_grpc + +from opensnitch.dialogs.prompt import PromptDialog +from opensnitch.dialogs.stats import StatsDialog + +from opensnitch.notifications import DesktopNotifications +from opensnitch.nodes import Nodes +from opensnitch.config import Config +from opensnitch.version import version +from opensnitch.database import Database +from opensnitch.utils import Utils, CleanerTask +from opensnitch.utils import Message + +class UIService(ui_pb2_grpc.UIServicer, QtWidgets.QGraphicsObject): + _new_remote_trigger = QtCore.pyqtSignal(str, ui_pb2.PingRequest) + _node_actions_trigger = QtCore.pyqtSignal(dict) + _update_stats_trigger = QtCore.pyqtSignal(str, str, ui_pb2.PingRequest) + _version_warning_trigger = QtCore.pyqtSignal(str, str) + _status_change_trigger = QtCore.pyqtSignal(bool) + _notification_callback = QtCore.pyqtSignal(ui_pb2.NotificationReply) + _show_message_trigger = QtCore.pyqtSignal(str, str, int) + + # .desktop filename located under /usr/share/applications/ + DESKTOP_FILENAME = "opensnitch_ui.desktop" + + def __init__(self, app, on_exit): + super(UIService, self).__init__() + + + self.MENU_ENTRY_STATS = QtCore.QCoreApplication.translate("contextual_menu", "Statistics") + self.MENU_ENTRY_FW_ENABLE = QtCore.QCoreApplication.translate("contextual_menu", "Enable") + self.MENU_ENTRY_FW_DISABLE = QtCore.QCoreApplication.translate("contextual_menu", "Disable") + self.MENU_ENTRY_HELP = QtCore.QCoreApplication.translate("contextual_menu", "Help") + self.MENU_ENTRY_CLOSE = QtCore.QCoreApplication.translate("contextual_menu", "Close") + + # set of actions that must be performed on the main thread + self.NODE_ADD = 0 + self.NODE_UPDATE = 1 + self.NODE_DELETE = 2 + self.ADD_RULE = 3 + self.DELETE_RULE = 4 + + self._cfg = Config.init() + self._db = Database.instance() + db_file=self._cfg.getSettings(self._cfg.DEFAULT_DB_FILE_KEY) + db_status, db_error = self._db.initialize( + dbtype=self._cfg.getInt(self._cfg.DEFAULT_DB_TYPE_KEY), + dbfile=db_file + ) + if db_status is False: + Message.ok( + QtCore.QCoreApplication.translate("preferences", "Warning"), + QtCore.QCoreApplication.translate("preferences", + "The DB is corrupted and it's not safe to continue.<br>\ + Remove, backup or recover the file before continuing.<br><br>\ + Corrupted database file: {0}".format(db_file)), + QtWidgets.QMessageBox.Warning) + sys.exit(-1) + + self._db_sqlite = self._db.get_db() + self._last_ping = None + self._version_warning_shown = False + self._asking = False + self._connected = False + self._fw_enabled = False + self._path = os.path.abspath(os.path.dirname(__file__)) + self._app = app + self._on_exit = on_exit + self._exit = False + self._msg = QtWidgets.QMessageBox() + self._remote_lock = Lock() + self._remote_stats = {} + + self._desktop_notifications = DesktopNotifications() + self._setup_interfaces() + self._setup_icons() + self._prompt_dialog = PromptDialog(appicon=self.white_icon) + self._stats_dialog = StatsDialog(dbname="general", db=self._db, appicon=self.white_icon) + self._setup_tray() + self._setup_slots() + + self._nodes = Nodes.instance() + + self._last_stats = {} + self._last_items = { + 'hosts':{}, + 'procs':{}, + 'addrs':{}, + 'ports':{}, + 'users':{} + } + + self._show_gui_if_tray_not_available() + + self._cleaner = None + if self._cfg.getBool(Config.DEFAULT_DB_PURGE_OLDEST): + self._start_db_cleaner() + + # https://gist.github.com/pklaus/289646 + def _setup_interfaces(self): + namestr, outbytes = Utils.get_interfaces() + self._interfaces = {} + for i in range(0, outbytes, 40): + name = namestr[i:i+16].split(b'\0', 1)[0] + addr = namestr[i+20:i+24] + self._interfaces[name] = "%d.%d.%d.%d" % (int(addr[0]), int(addr[1]), int(addr[2]), int(addr[3])) + + def _setup_slots(self): + # https://stackoverflow.com/questions/40288921/pyqt-after-messagebox-application-quits-why + self._app.setQuitOnLastWindowClosed(False) + self._version_warning_trigger.connect(self._on_diff_versions) + self._new_remote_trigger.connect(self._on_new_remote) + self._node_actions_trigger.connect(self._on_node_actions) + self._update_stats_trigger.connect(self._on_update_stats) + self._status_change_trigger.connect(self._on_status_changed) + self._stats_dialog._shown_trigger.connect(self._on_stats_dialog_shown) + self._stats_dialog._status_changed_trigger.connect(self._on_stats_status_changed) + self._stats_dialog.settings_saved.connect(self._on_settings_saved) + self._show_message_trigger.connect(self._show_systray_message) + + def _setup_icons(self): + self.off_image = QtGui.QPixmap(os.path.join(self._path, "res/icon-off.png")) + self.off_icon = QtGui.QIcon() + self.off_icon.addPixmap(self.off_image, QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.white_image = QtGui.QPixmap(os.path.join(self._path, "res/icon-white.svg")) + self.white_icon = QtGui.QIcon() + self.white_icon.addPixmap(self.white_image, QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.red_image = QtGui.QPixmap(os.path.join(self._path, "res/icon-red.png")) + self.red_icon = QtGui.QIcon() + self.red_icon.addPixmap(self.red_image, QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.pause_image = QtGui.QPixmap(os.path.join(self._path, "res/icon-pause.png")) + self.pause_icon = QtGui.QIcon() + self.pause_icon.addPixmap(self.pause_image, QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.alert_image = QtGui.QPixmap(os.path.join(self._path, "res/icon-alert.png")) + self.alert_icon = QtGui.QIcon() + self.alert_icon.addPixmap(self.alert_image, QtGui.QIcon.Normal, QtGui.QIcon.Off) + + self._app.setWindowIcon(self.white_icon) + # NOTE: only available since pyqt 5.7 + if hasattr(self._app, "setDesktopFileName"): + self._app.setDesktopFileName(self.DESKTOP_FILENAME) + + def _setup_tray(self): + self._tray = QtWidgets.QSystemTrayIcon(self.off_icon) + self._tray.show() + + self._menu = QtWidgets.QMenu() + self._tray.setContextMenu(self._menu) + self._tray.activated.connect(self._on_tray_icon_activated) + + self._menu.addAction(self.MENU_ENTRY_STATS).triggered.connect(self._show_stats_dialog) + self._menu_enable_fw = self._menu.addAction(self.MENU_ENTRY_FW_DISABLE) + self._menu_enable_fw.setEnabled(False) + self._menu_enable_fw.triggered.connect(self._on_enable_interception_clicked) + self._menu.addAction(self.MENU_ENTRY_HELP).triggered.connect( + lambda: QtGui.QDesktopServices.openUrl(QtCore.QUrl(Config.HELP_CONFIG_URL)) + ) + self._menu.addAction(self.MENU_ENTRY_CLOSE).triggered.connect(self._on_close) + + def _show_gui_if_tray_not_available(self): + """If the system tray is not available or ready, show the GUI after + 10s. This delay helps to skip showing up the GUI when DEs' autologin is on. + """ + tray = self._tray + gui = self._stats_dialog + def __show_gui(): + if not tray.isSystemTrayAvailable(): + gui.show() + + QtCore.QTimer.singleShot(10000, __show_gui) + + def _on_tray_icon_activated(self, reason): + if reason == QtWidgets.QSystemTrayIcon.Trigger or reason == QtWidgets.QSystemTrayIcon.MiddleClick: + if self._stats_dialog.isVisible() and not self._stats_dialog.isMinimized(): + self._stats_dialog.hide() + elif self._stats_dialog.isVisible() and self._stats_dialog.isMinimized() and not self._stats_dialog.isMaximized(): + self._stats_dialog.hide() + self._stats_dialog.showNormal() + elif self._stats_dialog.isVisible() and self._stats_dialog.isMinimized() and self._stats_dialog.isMaximized(): + self._stats_dialog.hide() + self._stats_dialog.showMaximized() + else: + self._stats_dialog.show() + + def _on_close(self): + self._exit = True + self._tray.setIcon(self.off_icon) + self._app.processEvents() + self._nodes.stop_notifications() + self._nodes.update_all(Nodes.OFFLINE) + self._db.vacuum() + self._db.optimize() + self._db.close() + self._stop_db_cleaner() + self._on_exit() + + def _show_stats_dialog(self): + if self._connected and self._fw_enabled: + self._tray.setIcon(self.white_icon) + self._stats_dialog.show() + + @QtCore.pyqtSlot(bool) + def _on_stats_status_changed(self, enabled): + self._update_fw_status(enabled) + + @QtCore.pyqtSlot(bool) + def _on_status_changed(self, enabled): + self._set_daemon_connected(enabled) + + @QtCore.pyqtSlot(str, str) + def _on_diff_versions(self, daemon_ver, ui_ver): + if self._version_warning_shown == False: + self._msg.setIcon(QtWidgets.QMessageBox.Warning) + self._msg.setWindowTitle("OpenSnitch version mismatch!") + self._msg.setText(("You are running version <b>%s</b> of the daemon, while the UI is at version " + \ + "<b>%s</b>, they might not be fully compatible.") % (daemon_ver, ui_ver)) + self._msg.setStandardButtons(QtWidgets.QMessageBox.Ok) + self._msg.show() + self._version_warning_shown = True + + @QtCore.pyqtSlot(str, str, ui_pb2.PingRequest) + def _on_update_stats(self, proto, addr, request): + main_need_refresh, details_need_refresh = self._populate_stats(self._db, proto, addr, request.stats) + is_local_request = self._is_local_request(proto, addr) + self._stats_dialog.update(is_local_request, request.stats, main_need_refresh or details_need_refresh) + + @QtCore.pyqtSlot(str, ui_pb2.PingRequest) + def _on_new_remote(self, addr, request): + self._remote_stats[addr] = { + 'last_ping': datetime.now(), + 'dialog': StatsDialog(address=addr, dbname=addr, db=self._db) + } + self._remote_stats[addr]['dialog'].daemon_connected = True + self._remote_stats[addr]['dialog'].update(addr, request.stats) + self._remote_stats[addr]['dialog'].show() + + @QtCore.pyqtSlot() + def _on_stats_dialog_shown(self): + if self._connected: + if self._fw_enabled: + self._tray.setIcon(self.white_icon) + else: + self._tray.setIcon(self.pause_icon) + else: + self._tray.setIcon(self.off_icon) + + @QtCore.pyqtSlot(ui_pb2.NotificationReply) + def _on_notification_reply(self, reply): + if reply.code == ui_pb2.ERROR: + self._tray.showMessage("Error", + reply.data, + QtWidgets.QSystemTrayIcon.Information, + 5000) + + def _on_remote_stats_menu(self, address): + self._remote_stats[address]['dialog'].show() + + @QtCore.pyqtSlot(str, str, int) + def _show_systray_message(self, title, body, icon): + if self._desktop_notifications.are_enabled(): + timeout = self._cfg.getInt(Config.DEFAULT_TIMEOUT_KEY, 15) + + if self._desktop_notifications.is_available() and self._cfg.getInt(Config.NOTIFICATIONS_TYPE, 1) == Config.NOTIFICATION_TYPE_SYSTEM: + self._desktop_notifications.show(title, body, os.path.join(self._path, "res/icon-white.svg")) + else: + self._tray.showMessage(title, body, icon, timeout * 1000) + + if icon == QtWidgets.QSystemTrayIcon.NoIcon: + self._tray.setIcon(self.alert_icon) + + def _on_enable_interception_clicked(self): + self._enable_interception(self._fw_enabled) + + @QtCore.pyqtSlot() + def _on_settings_saved(self): + if self._cfg.getBool(Config.DEFAULT_DB_PURGE_OLDEST): + if self._cleaner != None: + self._stop_db_cleaner() + self._start_db_cleaner() + elif self._cfg.getBool(Config.DEFAULT_DB_PURGE_OLDEST) == False and self._cleaner != None: + self._stop_db_cleaner() + + def _stop_db_cleaner(self): + if self._cleaner != None: + self._cleaner.stop() + self._cleaner = None + + def _start_db_cleaner(self): + def _cleaner_task(db): + oldest = self._cfg.getInt(self._cfg.DEFAULT_DB_MAX_DAYS, 1) + db.purge_oldest(oldest) + + interval = self._cfg.getInt(self._cfg.DEFAULT_DB_PURGE_INTERVAL, 5) + self._cleaner = CleanerTask(interval, _cleaner_task) + self._cleaner.start() + + def _update_fw_status(self, enabled): + """_update_fw_status updates the status of the menu entry + to disable or enable the firewall of the daemon. + """ + self._fw_enabled = enabled + if self._connected == False: + return + + self._stats_dialog.update_interception_status(enabled) + if enabled: + self._tray.setIcon(self.white_icon) + self._menu_enable_fw.setText(self.MENU_ENTRY_FW_DISABLE) + else: + self._tray.setIcon(self.pause_icon) + self._menu_enable_fw.setText(self.MENU_ENTRY_FW_ENABLE) + + def _set_daemon_connected(self, connected): + """_set_daemon_connected only updates the connection status of the daemon(s), + regardless if the fw is enabled or not. + There're 3 states: + - daemon connected + - daemon not connected + - daemon connected and firewall enabled/disabled + """ + self._stats_dialog.daemon_connected = connected + self._connected = connected + + # if there're more than 1 node, override connection status + if self._nodes.count() >= 1: + self._connected = True + self._stats_dialog.daemon_connected = True + + if self._nodes.count() == 1: + self._menu_enable_fw.setEnabled(True) + + if self._nodes.count() == 0 or self._nodes.count() > 1: + self._menu_enable_fw.setEnabled(False) + + self._stats_dialog.update_status() + + if self._connected: + self._tray.setIcon(self.white_icon) + else: + self._fw_enabled = False + self._tray.setIcon(self.off_icon) + + def _enable_interception(self, enable): + if self._connected == False: + return + if self._nodes.count() == 0: + self._tray.showMessage("No nodes connected", + "", + QtWidgets.QSystemTrayIcon.Information, + 5000) + return + if self._nodes.count() > 1: + print("enable interception for all nodes not supported yet") + return + + if enable: + nid, noti = self._nodes.stop_interception(_callback=self._notification_callback) + else: + nid, noti = self._nodes.start_interception(_callback=self._notification_callback) + + self._fw_enabled = not enable + + self._stats_dialog._status_changed_trigger.emit(not enable) + + def _is_local_request(self, proto, addr): + if proto == "unix": + return True + + elif proto == "ipv4" or proto == "ipv6": + for name, ip in self._interfaces.items(): + if addr == ip: + return True + + return False + + def _get_peer(self, peer): + """ + server -> client + 127.0.0.1:50051 -> ipv4:127.0.0.1:52032 + [::]:50051 -> ipv6:[::1]:59680 + 0.0.0.0:50051 -> ipv6:[::1]:59654 + """ + return self._nodes.get_addr(peer) + + def _delete_node(self, peer): + try: + proto, addr = self._get_peer(peer) + if addr in self._last_stats: + del self._last_stats[addr] + for table in self._last_items: + if addr in self._last_items[table]: + del self._last_items[table][addr] + + self._nodes.update(proto, addr, Nodes.OFFLINE) + self._nodes.delete(peer) + self._stats_dialog.update(True, None, True) + except Exception as e: + print("_delete_node() exception:", e) + + def _populate_stats(self, db, proto, addr, stats): + main_need_refresh = False + details_need_refresh = False + try: + if db == None: + print("populate_stats() db None") + return main_need_refresh, details_need_refresh + + _node = self._nodes.get_node(proto+":"+addr) + if _node == None: + return main_need_refresh, details_need_refresh + + # TODO: move to nodes.add_node() + version = _node['data'].version if _node != None else "" + hostname = _node['data'].name if _node != None else "" + db.insert("nodes", + "(addr, status, hostname, daemon_version, daemon_uptime, " \ + "daemon_rules, cons, cons_dropped, version, last_connection)", + (addr, Nodes.ONLINE, hostname, stats.daemon_version, str(timedelta(seconds=stats.uptime)), + stats.rules, stats.connections, stats.dropped, + version, datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) + + if addr not in self._last_stats: + self._last_stats[addr] = [] + + db.transaction() + for event in stats.events: + if event.unixnano in self._last_stats[addr]: + continue + main_need_refresh=True + db.insert("connections", + "(time, node, action, protocol, src_ip, src_port, dst_ip, dst_host, dst_port, uid, pid, process, process_args, process_cwd, rule)", + (str(datetime.fromtimestamp(event.unixnano/1000000000)), "%s:%s" % (proto, addr), event.rule.action, + event.connection.protocol, event.connection.src_ip, str(event.connection.src_port), + event.connection.dst_ip, event.connection.dst_host, str(event.connection.dst_port), + str(event.connection.user_id), str(event.connection.process_id), + event.connection.process_path, " ".join(event.connection.process_args), + event.connection.process_cwd, event.rule.name), + action_on_conflict="IGNORE" + ) + self._nodes.update_rule_time( + str(datetime.fromtimestamp(event.unixnano/1000000000)), + event.rule.name, + "%s:%s" % (proto, addr) + ) + db.commit() + + details_need_refresh = self._populate_stats_details(db, addr, stats) + self._last_stats[addr] = [] + for event in stats.events: + self._last_stats[addr].append(event.unixnano) + except Exception as e: + print("_populate_stats() exception: ", e) + + return main_need_refresh, details_need_refresh + + def _populate_stats_details(self, db, addr, stats): + need_refresh = False + changed = self._populate_stats_events(db, addr, stats, "hosts", ("what", "hits"), (1,2), stats.by_host.items()) + if changed: need_refresh = True + changed = self._populate_stats_events(db, addr, stats, "procs", ("what", "hits"), (1,2), stats.by_executable.items()) + if changed: need_refresh = True + changed = self._populate_stats_events(db, addr, stats, "addrs", ("what", "hits"), (1,2), stats.by_address.items()) + if changed: need_refresh = True + changed = self._populate_stats_events(db, addr, stats, "ports", ("what", "hits"), (1,2), stats.by_port.items()) + if changed: need_refresh = True + changed = self._populate_stats_events(db, addr, stats, "users", ("what", "hits"), (1,2), stats.by_uid.items()) + if changed: need_refresh = True + + return need_refresh + + def _populate_stats_events(self, db, addr, stats, table, colnames, cols, items): + fields = [] + values = [] + need_refresh = False + try: + if addr not in self._last_items[table].keys(): + self._last_items[table][addr] = {} + if items == self._last_items[table][addr]: + return need_refresh + + for row, event in enumerate(items): + if event in self._last_items[table][addr]: + continue + need_refresh = True + what, hits = event + # FIXME: this is suboptimal + # BUG: there can be users with same id on different machines but with different names + if table == "users": + what = Utils.get_user_id(what) + fields.append(what) + values.append(int(hits)) + # FIXME: default action on conflict is to replace. If there're multiple nodes connected, + # stats are painted once per node on each update. + if need_refresh: + db.insert_batch(table, colnames, cols, fields, values) + + self._last_items[table][addr] = items + except Exception as e: + print("details exception: ", e) + + return need_refresh + + def _overwrite_nodes_config(self, node_config): + _default_action = self._cfg.getInt(self._cfg.DEFAULT_ACTION_KEY) + temp_cfg = json.loads(node_config) + try: + if _default_action == Config.ACTION_DENY_IDX: + temp_cfg['DefaultAction'] = Config.ACTION_DENY + else: + temp_cfg['DefaultAction'] = Config.ACTION_ALLOW + + node_config = json.dumps(temp_cfg) + except Exception as e: + print("error parsing node's configuration:", e) + + return node_config + + @QtCore.pyqtSlot(dict) + def _on_node_actions(self, kwargs): + if kwargs['action'] == self.NODE_ADD: + n = self._nodes.add(kwargs['peer'], kwargs['node_config']) + if n != None: + self._status_change_trigger.emit(True) + # if there're more than one node, we can't update the status + # based on the fw status, only if the daemon is running or not + if self._nodes.count() <= 1: + self._update_fw_status(kwargs['node_config'].isFirewallRunning) + else: + self._update_fw_status(True) + elif kwargs['action'] == self.ADD_RULE: + rule = kwargs['rule'] + proto, addr = self._get_peer(kwargs['peer']) + self._nodes.add_rule((datetime.now().strftime("%Y-%m-%d %H:%M:%S")), + "{0}:{1}".format(proto, addr), + rule.name, str(rule.enabled), str(rule.precedence), rule.action, rule.duration, + rule.operator.type, str(rule.operator.sensitive), rule.operator.operand, + rule.operator.data) + elif kwargs['action'] == self.DELETE_RULE: + self._db.delete_rule(kwargs['name'], kwargs['addr']) + + elif kwargs['action'] == self.NODE_DELETE: + self._delete_node(kwargs['peer']) + + def Ping(self, request, context): + try: + self._last_ping = datetime.now() + if Utils.check_versions(request.stats.daemon_version): + self._version_warning_trigger.emit(request.stats.daemon_version, version) + + proto, addr = self._get_peer(context.peer()) + # do not update db here, do it on the main thread + self._update_stats_trigger.emit(proto, addr, request) + #else: + # with self._remote_lock: + # # XXX: disable this option for now + # # opening several dialogs only updates one of them. + # if addr not in self._remote_stats: + # self._new_remote_trigger.emit(addr, request) + # else: + # self._populate_stats(self._remote_stats[addr]['dialog'].get_db(), proto, addr, request.stats) + # self._remote_stats[addr]['dialog'].update(addr, request.stats) + + except Exception as e: + print("Ping exception: ", e) + + return ui_pb2.PingReply(id=request.id) + + def AskRule(self, request, context): + #def callback(ntf, action, connection): + # TODO + + #if self._desktop_notifications.support_actions(): + # self._desktop_notifications.ask(request, callback) + + # TODO: allow connections originated from ourselves: os.getpid() == request.pid) + self._asking = True + proto, addr = self._get_peer(context.peer()) + rule, timeout_triggered = self._prompt_dialog.promptUser(request, self._is_local_request(proto, addr), context.peer()) + self._last_ping = datetime.now() + self._asking = False + if rule == None: + return None + + if timeout_triggered: + _title = request.process_path + if _title == "": + _title = "%s:%d (%s)" % (request.dst_host if request.dst_host != "" else request.dst_ip, request.dst_port, request.protocol) + + + node_text = "" if self._is_local_request(proto, addr) else "on node {0}:{1}".format(proto, addr) + self._show_message_trigger.emit(_title, + "{0} action applied {1}\nCommand line: {2}" + .format(rule.action, node_text, " ".join(request.process_args)), + QtWidgets.QSystemTrayIcon.NoIcon) + + + if rule.duration in Config.RULES_DURATION_FILTER: + self._node_actions_trigger.emit( + { + 'action': self.DELETE_RULE, + 'name': rule.name, + 'addr': context.peer() + } + ) + else: + self._node_actions_trigger.emit( + { + 'action': self.ADD_RULE, + 'peer': context.peer(), + 'rule': rule + } + ) + + return rule + + def Subscribe(self, node_config, context): + """ + Accept and collect nodes. It keeps a connection open with each + client, in order to send them notifications. + + @doc: https://grpc.github.io/grpc/python/grpc.html#service-side-context + """ + # if the exit mark is set, don't accept new connections. + # db vacuum operation may take a lot of time to complete. + if self._exit: + return + try: + self._node_actions_trigger.emit({ + 'action': self.NODE_ADD, + 'peer': context.peer(), + 'node_config': node_config + }) + # force events processing, to add the node ^ before the + # Notifications() call arrives. + self._app.processEvents() + + proto, addr = self._get_peer(context.peer()) + if self._is_local_request(proto, addr) == False: + self._show_message_trigger.emit( + QtCore.QCoreApplication.translate("stats", "New node connected"), + "({0})".format(context.peer()), + QtWidgets.QSystemTrayIcon.Information) + except Exception as e: + print("[Notifications] exception adding new node:", e) + context.cancel() + + node_config.config = self._overwrite_nodes_config(node_config.config) + + return node_config + + def Notifications(self, node_iter, context): + """ + Accept and collect nodes. It keeps a connection open with each + client, in order to send them notifications. + + @doc: https://grpc.github.io/grpc/python/grpc.html#service-side-context + @doc: https://grpc.io/docs/what-is-grpc/core-concepts/ + """ + proto, addr = self._get_peer(context.peer()) + _node = self._nodes.get_node("%s:%s" % (proto, addr)) + if _node == None: + return + + stop_event = Event() + def _on_client_closed(): + stop_event.set() + self._node_actions_trigger.emit( + {'action': self.NODE_DELETE, + 'peer': context.peer(), + }) + + self._status_change_trigger.emit(False) + # TODO: handle the situation when a node disconnects, and the + # remaining node has the fw disabled. + #if self._nodes.count() == 1: + # nd = self._nodes.get_nodes() + # if nd[0].get_config().isFirewallRunning: + + if self._is_local_request(proto, addr) == False: + self._show_message_trigger.emit("node exited", + "({0})".format(context.peer()), + QtWidgets.QSystemTrayIcon.Information) + + context.add_callback(_on_client_closed) + + # TODO: move to notifications.py + def new_node_message(): + print("new node connected, listening for client responses...", addr) + + while self._exit == False: + try: + if stop_event.is_set(): + break + in_message = next(node_iter) + if in_message == None: + continue + + self._nodes.reply_notification(addr, in_message) + except StopIteration: + print("[Notifications] Node {0} exited".format(addr)) + break + except grpc.RpcError as e: + print("[Notifications] grpc exception new_node_message(): ", addr, in_message) + except Exception as e: + print("[Notifications] unexpected exception new_node_message(): ", addr, e, in_message) + + read_thread = Thread(target=new_node_message) + read_thread.daemon = True + read_thread.start() + + while self._exit == False: + if stop_event.is_set(): + break + + try: + noti = _node['notifications'].get() + if noti != None: + _node['notifications'].task_done() + yield noti + except Exception as e: + print("[Notifications] exception getting notification from queue:", addr, e) + context.cancel() + + return node_iter + diff --git a/ui/opensnitch/utils.py b/ui/opensnitch/utils.py new file mode 100644 index 0000000..2926752 --- /dev/null +++ b/ui/opensnitch/utils.py @@ -0,0 +1,293 @@ + +from PyQt5 import QtCore, QtWidgets, QtGui +from opensnitch.version import version +from opensnitch.database import Database +from opensnitch.config import Config +from threading import Thread, Event +import pwd +import socket +import fcntl +import struct +import array +import os, sys, glob + +class AsnDB(): + __instance = None + asndb = None + + @staticmethod + def instance(): + if AsnDB.__instance == None: + AsnDB.__instance = AsnDB() + return AsnDB.__instance + + def __init__(self): + self.ASN_AVAILABLE = True + self.load() + + def is_available(self): + return self.ASN_AVAILABLE + + def load(self): + """Load the ASN DB from disk. + + It'll try to load it from user's opensnitch directory if these file exist: + - ~/.config/opensnitch/ipasn_db.dat.gz + - ~/.config/opensnitch/asnames.json + Otherwise it'll try to load it from python3-pyasn package. + """ + try: + if self.asndb != None: + return + + import pyasn + + IPASN_DB_PATH = os.path.expanduser('~/.config/opensnitch/ipasn_db.dat.gz') + # .gz not supported for asnames + AS_NAMES_FILE_PATH = os.path.expanduser('~/.config/opensnitch/asnames.json') + + # if the user hasn't downloaded an updated ipasn db, use the one + # shipped with the python3-pyasn package + if os.path.isfile(IPASN_DB_PATH) == False: + IPASN_DB_PATH = '/usr/lib/python3/dist-packages/data/ipasn_20140513_v12.dat.gz' + if os.path.isfile(AS_NAMES_FILE_PATH) == False: + AS_NAMES_FILE_PATH = '/usr/lib/python3/dist-packages/data/asnames.json' + + print("using IPASN DB:", IPASN_DB_PATH) + self.asndb = pyasn.pyasn(IPASN_DB_PATH, as_names_file=AS_NAMES_FILE_PATH) + except Exception as e: + self.ASN_AVAILABLE = False + print("exception loading ipasn db:", e) + print("Install python3-pyasn to display IP's network name.") + + + def lookup(self, ip): + """Lookup the IP in the ASN DB. + + Return the net range and the prefix if found, otherwise nothing. + """ + try: + return self.asndb.lookup(ip) + except Exception: + return "", "" + + def get_as_name(self, asn): + """Get the ASN name given a network range. + + Return the name of the network if found, otherwise nothing. + """ + try: + asname = self.asndb.get_as_name(asn) + if asname == None: + asname = "" + return asname + except Exception: + return "" + + def get_asn(self, ip): + try: + asn, prefix = self.lookup(ip) + return self.get_as_name(asn) + except Exception: + return "" + +class Themes(): + """Change GUI's appearance using qt-material lib. + https://github.com/UN-GCPDS/qt-material + """ + THEMES_PATH = [ + os.path.expanduser("~/.config/opensnitch/"), + os.path.dirname(sys.modules[__name__].__file__) + ] + __instance = None + + AVAILABLE = False + try: + from qt_material import apply_stylesheet as qtmaterial_apply_stylesheet + from qt_material import list_themes as qtmaterial_themes + AVAILABLE = True + except Exception: + print("Themes not available. Install qt-material if you want to change GUI's appearance: pip3 install qt-material.") + + @staticmethod + def instance(): + if Themes.__instance == None: + Themes.__instance = Themes() + return Themes.__instance + + def __init__(self): + self._cfg = Config.get() + theme = self._cfg.getInt(self._cfg.DEFAULT_THEME, 0) + + def available(self): + return Themes.AVAILABLE + + def get_saved_theme(self): + if not Themes.AVAILABLE: + return 0, "" + + theme = self._cfg.getSettings(self._cfg.DEFAULT_THEME) + if theme != "" and theme != None: + # 0 == System + return self.list_themes().index(theme)+1, theme + return 0, "" + + def save_theme(self, theme_idx, theme): + if not Themes.AVAILABLE: + return + + if theme_idx == 0: + self._cfg.setSettings(self._cfg.DEFAULT_THEME, "") + else: + self._cfg.setSettings(self._cfg.DEFAULT_THEME, theme) + + def load_theme(self, app): + if not Themes.AVAILABLE: + return + + try: + theme_idx, theme_name = self.get_saved_theme() + if theme_name != "": + invert = "light" in theme_name + print("Using theme:", theme_idx, theme_name, "inverted:", invert) + # TODO: load {theme}.xml.extra and .xml.css for further + # customizations. + Themes.qtmaterial_apply_stylesheet(app, theme=theme_name, invert_secondary=invert) + except Exception as e: + print("Themes.load_theme() exception:", e) + + def list_local_themes(self): + themes = [] + if not Themes.AVAILABLE: + return themes + + try: + for tdir in self.THEMES_PATH: + themes += glob.glob(tdir + "/themes/*.xml") + except Exception: + pass + finally: + return themes + + def list_themes(self): + themes = self.list_local_themes() + if not Themes.AVAILABLE: + return themes + + themes += Themes.qtmaterial_themes() + return themes + +class CleanerTask(Thread): + interval = 1 + stop_flag = None + callback = None + + def __init__(self, _interval, _callback): + Thread.__init__(self, name="cleaner_db_thread") + self.interval = _interval * 60 + self.stop_flag = Event() + self.callback = _callback + self._cfg = Config.init() + + # We need to instantiate a new QsqlDatabase object with a unique name, + # because it's not thread safe: + # "A connection can only be used from within the thread that created it." + # https://doc.qt.io/qt-5/threads-modules.html#threads-and-the-sql-module + # The filename and type is the same, the one chosen by the user. + self.db = Database("db-cleaner-connection") + self.db_status, db_error = self.db.initialize( + dbtype=self._cfg.getInt(self._cfg.DEFAULT_DB_TYPE_KEY), + dbfile=self._cfg.getSettings(self._cfg.DEFAULT_DB_FILE_KEY) + ) + + def run(self): + if self.db_status == False: + return + while not self.stop_flag.is_set(): + self.stop_flag.wait(self.interval) + self.callback(self.db) + + def stop(self): + self.stop_flag.set() + self.db.close() + +class QuickHelp(): + @staticmethod + def show(help_str): + QtWidgets.QToolTip.showText(QtGui.QCursor.pos(), help_str) + +class Utils(): + @staticmethod + def check_versions(daemon_version): + lMayor, lMinor, lPatch = version.split(".") + rMayor, rMinor, rPatch = daemon_version.split(".") + return lMayor != rMayor or (lMayor == rMayor and lMinor != rMinor) + + @staticmethod + def get_user_id(uid): + pw_name = uid + try: + pw_name = pwd.getpwuid(int(uid)).pw_name + " (" + uid + ")" + except Exception: + #pw_name += " (error)" + pass + + return pw_name + + @staticmethod + def get_interfaces(): + max_possible = 128 # arbitrary. raise if needed. + bytes = max_possible * 32 + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + names = array.array('B', b'\0' * bytes) + outbytes = struct.unpack('iL', fcntl.ioctl( + s.fileno(), + 0x8912, # SIOCGIFCONF + struct.pack('iL', bytes, names.buffer_info()[0]) + ))[0] + return names.tobytes(), outbytes + +class Message(): + + @staticmethod + def ok(title, message, icon): + msgBox = QtWidgets.QMessageBox() + msgBox.setWindowFlags(msgBox.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) + msgBox.setText("<b>{0}</b><br><br>{1}".format(title, message)) + msgBox.setIcon(icon) + msgBox.setModal(True) + msgBox.setStandardButtons(QtWidgets.QMessageBox.Ok) + msgBox.exec_() + + @staticmethod + def yes_no(title, message, icon): + msgBox = QtWidgets.QMessageBox() + msgBox.setWindowFlags(msgBox.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) + msgBox.setText(title) + msgBox.setIcon(icon) + msgBox.setModal(True) + msgBox.setInformativeText(message) + msgBox.setStandardButtons(QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.Yes) + msgBox.setDefaultButton(QtWidgets.QMessageBox.Cancel) + return msgBox.exec_() + +class FileDialog(): + + @staticmethod + def save(parent): + options = QtWidgets.QFileDialog.Options() + fileName, _ = QtWidgets.QFileDialog.getSaveFileName(parent, "", "","All Files (*)", options=options) + return fileName + + @staticmethod + def select(parent): + options = QtWidgets.QFileDialog.Options() + fileName, _ = QtWidgets.QFileDialog.getOpenFileName(parent, "", "","All Files (*)", options=options) + return fileName + + @staticmethod + def select_dir(parent, current_dir): + options = QtWidgets.QFileDialog.Options() + fileName = QtWidgets.QFileDialog.getExistingDirectory(parent, "", current_dir, options) + return fileName + diff --git a/ui/opensnitch/version.py b/ui/opensnitch/version.py new file mode 100644 index 0000000..5192bae --- /dev/null +++ b/ui/opensnitch/version.py @@ -0,0 +1 @@ +version = '1.5.8' diff --git a/ui/requirements.txt b/ui/requirements.txt new file mode 100644 index 0000000..f29cc1b --- /dev/null +++ b/ui/requirements.txt @@ -0,0 +1,5 @@ +grpcio-tools>=1.10.1 +pyinotify==0.9.6 +unicode_slugify==0.1.3 +pyqt5>=5.6 +protobuf diff --git a/ui/resources/icons/48x48/opensnitch-ui.png b/ui/resources/icons/48x48/opensnitch-ui.png new file mode 100644 index 0000000000000000000000000000000000000000..6dcbd5f39ac9aeb2e74dda7738f4e7ceb8b69fcd GIT binary patch literal 1834 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 Y521Kp>I377T>t<807*qoM6N<$f+5;}!T<mO literal 0 HcmV?d00001 diff --git a/ui/resources/icons/64x64/opensnitch-ui.png b/ui/resources/icons/64x64/opensnitch-ui.png new file mode 100644 index 0000000000000000000000000000000000000000..4b73e6b1c5f256d9d43884ad5d9e2b85b7f4909a GIT binary patch literal 2380 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 literal 0 HcmV?d00001 diff --git a/ui/resources/icons/opensnitch-ui.svg b/ui/resources/icons/opensnitch-ui.svg new file mode 100644 index 0000000..df12789 --- /dev/null +++ b/ui/resources/icons/opensnitch-ui.svg @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="64" + height="64" + viewBox="0 0 12.698413 12.698413" + version="1.1" + id="svg8" + inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" + sodipodi:docname="opensnitch-ui.svg" + inkscape:export-filename="64x64/opensnitch-ui.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <defs + id="defs2" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="9.75" + inkscape:cx="21.74359" + inkscape:cy="31.794872" + inkscape:document-units="px" + inkscape:current-layer="g4133" + showgrid="true" + inkscape:window-width="1600" + inkscape:window-height="839" + inkscape:window-x="0" + inkscape:window-y="24" + inkscape:window-maximized="1" + units="px" + inkscape:pagecheckerboard="0" + inkscape:showpageshadow="2" + inkscape:deskcolor="#d1d1d1" + showguides="true"> + <sodipodi:guide + position="13.436869,10.958536" + orientation="0,-1" + id="guide291" + inkscape:locked="false" /> + <sodipodi:guide + position="13.233163,1.6160318" + orientation="0,-1" + id="guide293" + inkscape:locked="false" /> + <inkscape:grid + type="xygrid" + id="grid295" /> + </sodipodi:namedview> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Capa 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(0,-284.30001)"> + <g + id="g4133" + transform="matrix(0.9182611,0,0,0.9182611,-19.582232,24.4642)"> + <path + style="fill:#232629;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.287462px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 23.93778,289.55608 0.922529,-2.47895 2.563056,-0.57549 2.395943,-1.27803 2.491924,1.17578 0.401143,2.50266 1.620734,1.26491 0.381256,1.78538 -0.666013,1.62872 -1.326357,0.85434 -9.105041,0.0948 -1.530218,-1.13976 -0.31061,-1.71767 0.780558,-1.56852 z" + id="path1481" + sodipodi:nodetypes="ccccccccccccccc" + inkscape:export-filename="/home/ga/Proiektuak/opensnitch/gui/opensnitch/ui/lib/python3.9/site-packages/opensnitch/res/icon-white.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" /> + <path + style="fill:#fbffff;fill-opacity:1;stroke:none;stroke-width:0.0267051;stroke-opacity:1" + d="m 23.924347,295.01517 c -1.190403,-0.17151 -2.160364,-1.04049 -2.48066,-2.22233 -0.06228,-0.22986 -0.06956,-0.30524 -0.06956,-0.7212 0,-0.41596 0.0072,-0.49134 0.06956,-0.72117 0.147237,-0.54329 0.408851,-0.99654 0.795944,-1.37896 0.324557,-0.32066 0.785409,-0.60618 1.154881,-0.71557 l 0.103224,-0.0303 0.01629,-0.29557 c 0.07143,-1.29674 0.972433,-2.37494 2.27078,-2.71736 0.229851,-0.0607 0.312438,-0.0688 0.716539,-0.0696 0.257818,-6.1e-4 0.509198,0.0123 0.577176,0.0292 0.120416,0.0303 0.120441,0.0303 0.198793,-0.0679 0.155946,-0.19524 0.509186,-0.51209 0.74373,-0.66706 0.551463,-0.36443 1.11147,-0.54697 1.774575,-0.57842 0.589067,-0.0277 1.121425,0.0806 1.650413,0.33595 0.72776,0.35149 1.243577,0.8611 1.599369,1.58011 0.255831,0.51699 0.367014,1.04428 0.341349,1.61884 l -0.01332,0.2989 0.217937,0.14177 c 0.119863,0.078 0.346565,0.26788 0.503782,0.42196 0.88975,0.87202 1.218873,2.08704 0.893766,3.29955 -0.132616,0.49457 -0.500547,1.11835 -0.881945,1.49513 -0.364404,0.36002 -1.001057,0.73363 -1.4664,0.86052 -0.492709,0.13439 -0.431399,0.13271 -4.634625,0.12938 -2.15745,-0.002 -3.994154,-0.0135 -4.081564,-0.0261 z m 8.365743,-0.89453 c 0.59725,-0.15778 1.128615,-0.51689 1.486943,-1.00491 0.147801,-0.20133 0.33126,-0.60206 0.39973,-0.87313 0.08448,-0.33442 0.08466,-0.85621 4.07e-4,-1.18961 -0.203279,-0.80452 -0.831632,-1.50173 -1.597272,-1.77233 -0.153083,-0.0541 -0.232971,-0.0955 -0.224805,-0.11652 0.252703,-0.65059 0.224901,-1.36673 -0.07754,-1.99749 -0.264858,-0.55243 -0.669292,-0.9522 -1.225766,-1.21167 -0.399047,-0.18607 -0.636305,-0.23736 -1.097982,-0.23736 -0.319298,0 -0.430775,0.0109 -0.61795,0.0601 -0.739241,0.19424 -1.358854,0.68643 -1.680928,1.33522 l -0.07478,0.15063 -0.122961,-0.0614 c -0.228775,-0.11421 -0.565847,-0.2025 -0.834911,-0.21871 -0.932118,-0.0562 -1.838882,0.55372 -2.13689,1.4371 -0.168757,0.50024 -0.151725,0.9779 0.05358,1.50243 0.01377,0.035 -0.0089,0.0394 -0.156728,0.03 -0.208357,-0.0132 -0.547143,0.0503 -0.81323,0.15241 -0.54704,0.20974 -1.043227,0.72489 -1.228933,1.27596 -0.172171,0.51087 -0.161028,0.97577 0.03539,1.47692 0.212384,0.54181 0.73321,1.03044 1.29576,1.21563 0.113249,0.0371 0.270424,0.0783 0.349276,0.0916 0.08114,0.0136 1.857609,0.0221 4.092874,0.0195 l 3.949506,-0.004 z m -5.761885,-1.40118 c -0.576304,-0.34169 -1.047827,-0.63319 -1.047827,-0.64782 0,-0.0148 0.471523,-0.30613 1.047827,-0.64783 l 1.047829,-0.62116 0.0074,0.42212 0.0074,0.42218 h 1.718841 1.718847 v 0.42469 0.42469 h -1.718876 -1.718849 l -0.0074,0.42218 -0.0074,0.42217 z m 2.350898,-2.34355 v -0.42777 h -1.719512 -1.719514 v -0.42468 -0.42471 h 1.718847 1.718849 l 0.0074,-0.42217 0.0074,-0.42216 1.047812,0.6212 c 0.5763,0.34168 1.051205,0.63124 1.055344,0.64353 0.0041,0.0124 -0.443196,0.28902 -0.99408,0.61507 -0.550883,0.32599 -1.028812,0.61 -1.062059,0.63111 l -0.06046,0.0382 z" + id="path826-6" + inkscape:connector-curvature="0" /> + </g> + </g> +</svg> diff --git a/ui/resources/io.github.evilsocket.opensnitch.appdata.xml b/ui/resources/io.github.evilsocket.opensnitch.appdata.xml new file mode 100644 index 0000000..b55aff9 --- /dev/null +++ b/ui/resources/io.github.evilsocket.opensnitch.appdata.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> +<component type="desktop-application"> + <id>io.github.evilsocket.opensnitch</id> + + <name>OpenSnitch</name> + <summary>GNU/Linux interactive application firewall</summary> + + <metadata_license>FTL</metadata_license> + <project_license>GPL-3.0-or-later</project_license> + + <supports> + <control>pointing</control> + <control>keyboard</control> + <control>touch</control> + </supports> + + <description> + <p> + Whenever a program tries to establish a new connection, it'll prompt the user to allow or deny it. + </p> + <p> + The user can decide if block the outgoing connection based on properties of the connection: by port, by uid, by dst ip, by program or a combination of them. These rules can last forever, until the app restart or just one time. + </p> + <p> + The GUI allows the user to view live outgoing connections, as well as search by process, user, host or port. + </p> + <p> + OpenSnitch can also work as a system-wide domains blocker, by using lists of domains, list of IPs or list of regular expressions. + </p> + </description> + + <categories> + <category>System</category> + <category>Security</category> + <category>Monitor</category> + <category>Network</category> + </categories> + + <icon type="stock">opensnitch-ui</icon> + <url type="homepage">https://github.com/evilsocket/opensnitch</url> + <url type="bugtracker">https://github.com/evilsocket/opensnitch/issues</url> + <url type="help">https://github.com/evilsocket/opensnitch/wiki</url> + <launchable type="desktop-id">opensnitch_ui.desktop</launchable> + <screenshots> + <screenshot type="default"> + <image>https://user-images.githubusercontent.com/2742953/85205382-6ba9cb00-b31b-11ea-8e9a-bd4b8b05a236.png</image> + </screenshot> + <screenshot> + <image>https://user-images.githubusercontent.com/2742953/217039798-3477c6c2-d64f-4eea-89af-cd94ee77cff4.png</image> + </screenshot> + <screenshot> + <image>https://user-images.githubusercontent.com/2742953/99863173-3987e800-2b9d-11eb-93f2-fe3121b18c51.png</image> + </screenshot> + </screenshots> + <content_rating type="oars-1.0" /> +</component> diff --git a/ui/resources/kcm_opensnitch.desktop b/ui/resources/kcm_opensnitch.desktop new file mode 100644 index 0000000..ee2c112 --- /dev/null +++ b/ui/resources/kcm_opensnitch.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Exec=opensnitch-ui +Icon=opensnitch-ui +Type=Service +X-KDE-ServiceTypes=SystemSettingsExternalApp +Name=OpenSnitch Firewall +Comment=OpenSnitch Firewall Graphical Interface +X-KDE-Keywords=system,firewall,policies,security,polkit,policykit,douane +X-KDE-Autostart-after=panel diff --git a/ui/resources/opensnitch_ui.desktop b/ui/resources/opensnitch_ui.desktop new file mode 100644 index 0000000..4105103 --- /dev/null +++ b/ui/resources/opensnitch_ui.desktop @@ -0,0 +1,18 @@ +[Desktop Entry] +Type=Application +Name=OpenSnitch +Exec=/bin/sh -c "pkill -15 opensnitch-ui; opensnitch-ui" +Icon=opensnitch-ui +GenericName=OpenSnitch Firewall +GenericName[hu]=OpenSnitch-tűzfal +GenericName[nb]=OpenSnitch brannmur +Comment=Interactive application firewall +Comment[es]=Firewall de aplicaciones +Comment[hu]=Alkalmazási tűzfal +Comment[nb]=Interaktiv programbrannmur +Terminal=false +NoDisplay=false +Categories=System;Security;Monitor;Network; +Keywords=system;firewall;policies;security;polkit;policykit; +X-GNOME-Autostart-Delay=3 +X-GNOME-Autostart-enabled=true diff --git a/ui/setup.py b/ui/setup.py new file mode 100644 index 0000000..ee11d73 --- /dev/null +++ b/ui/setup.py @@ -0,0 +1,38 @@ +from setuptools import setup, find_packages + +import os +import sys + +path = os.path.abspath(os.path.dirname(__file__)) +sys.path.append(path) + +from opensnitch.version import version + +setup(name='opensnitch-ui', + version=version, + description='Prompt service and UI for the opensnitch interactive firewall application.', + long_description='GUI for the opensnitch interactive firewall application\n\ +opensnitch-ui is a GUI for opensnitch written in Python.\n\ +It allows the user to view live outgoing connections, as well as search\n\ +to make connections.\n\ +.\n\ +The user can decide if block the outgoing connection based on properties of\n\ +the connection: by port, by uid, by dst ip, by program or a combination\n\ +of them.\n\ +.\n\ +These rules can last forever, until the app restart or just one time.', + url='https://github.com/evilsocket/opensnitch', + author='Simone "evilsocket" Margaritelli', + author_email='evilsocket@protonmail.com', + license='GPL-3.0', + packages=find_packages(), + include_package_data = True, + package_data={'': ['*.*']}, + data_files=[('/usr/share/applications', ['resources/opensnitch_ui.desktop']), + ('/usr/share/kservices5', ['resources/kcm_opensnitch.desktop']), + ('/usr/share/icons/hicolor/scalable/apps', ['resources/icons/opensnitch-ui.svg']), + ('/usr/share/icons/hicolor/48x48/apps', ['resources/icons/48x48/opensnitch-ui.png']), + ('/usr/share/icons/hicolor/64x64/apps', ['resources/icons/64x64/opensnitch-ui.png']), + ('/usr/share/metainfo', ['resources/io.github.evilsocket.opensnitch.appdata.xml'])], + scripts = [ 'bin/opensnitch-ui' ], + zip_safe=False) diff --git a/ui/tests/README.md b/ui/tests/README.md new file mode 100644 index 0000000..e94453b --- /dev/null +++ b/ui/tests/README.md @@ -0,0 +1,23 @@ +GUI unit tests. + +We use pytest [0] to pytest-qt [1] to test GUI code. + +To run the tests: `cd tests; pytest -v` + +TODO: + - test service class (Service.py) + - test events window (stats.py): + - The size of the window must be saved on close, and restored when opening it again. + - Columns width of every view must be saved and restored properly. + - On the Events tab, clicking on the Node, Process or Rule column must jump to the detailed view of the selected item. + - When entering into a detail view: + - the results limit configured must be respected (that little button on the bottom right of every tab). + - must apply the proper SQL query for every detailed view. + - When going back from a detail view: + - The SQL query must be restored. + - Test rules context menu actions. + - Test select rows and copy them to the clipboard (ctrl+c). + + +0. https://docs.pytest.org/en/6.2.x/ +1. https://pytest-qt.readthedocs.io/en/latest/intro.html diff --git a/ui/tests/__init__.py b/ui/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ui/tests/dialogs/__init__.py b/ui/tests/dialogs/__init__.py new file mode 100644 index 0000000..25d7970 --- /dev/null +++ b/ui/tests/dialogs/__init__.py @@ -0,0 +1,52 @@ + +from opensnitch.database import Database +from opensnitch.config import Config +from opensnitch.nodes import Nodes + +# grpc object +class ClientConfig: + version = "1.2.3" + name = "bla" + logLevel = 0 + isFirewallRunning = False + rules = [] + config = '''{ + "Server":{ + "Address": "unix:///tmp/osui.sock", + "LogFile": "/var/log/opensnitchd.log" + }, + "DefaultAction": "deny", + "DefaultDuration": "once", + "InterceptUnknown": false, + "ProcMonitorMethod": "ebpf", + "LogLevel": 0, + "Firewall": "iptables", + "Stats": { + "MaxEvents": 150, + "MaxStats": 50 + } + } + ''' + +class Connection: + protocol = "tcp" + src_ip = "127.0.0.1" + src_port = "12345" + dst_ip = "127.0.0.1" + dst_host = "localhost" + dst_port = "54321" + user_id = 1000 + process_id = 9876 + process_path = "/bin/cmd" + process_cwd = "/tmp" + process_args = "/bin/cmd --parm1 test" + process_env = [] + +db = Database.instance() +db.initialize() +Config.init() + +nodes = Nodes.instance() +nodes._nodes["unix:/tmp/osui.sock"] = { + 'data': ClientConfig +} diff --git a/ui/tests/dialogs/test_preferences.py b/ui/tests/dialogs/test_preferences.py new file mode 100644 index 0000000..6d4bc2f --- /dev/null +++ b/ui/tests/dialogs/test_preferences.py @@ -0,0 +1,142 @@ +# +# pytest -v tests/dialogs/test_ruleseditor.py +# +import os +import time +import json +from PyQt5 import QtCore, QtWidgets, QtGui + +from opensnitch.config import Config +from opensnitch.dialogs.preferences import PreferencesDialog + +class TestPreferences(): + + @classmethod + def reset_settings(self): + try: + os.remove(os.environ['HOME'] + "/.config/opensnitch/settings.conf") + except Exception: + pass + + @classmethod + def setup_method(self): + white_icon = QtGui.QIcon("../res/icon-white.svg") + self.reset_settings() + self.prefs = PreferencesDialog(appicon=white_icon) + self.prefs.show() + + def run(self, qtbot): + def handle_dialog(): + qtbot.mouseClick(self.prefs.applyButton, QtCore.Qt.LeftButton) + qtbot.mouseClick(self.prefs.acceptButton, QtCore.Qt.LeftButton) + + QtCore.QTimer.singleShot(500, handle_dialog) + self.prefs.exec_() + + def test_save_popups_settings(self, qtbot): + """ Test saving UI related settings. + """ + qtbot.addWidget(self.prefs) + + self.prefs.comboUIAction.setCurrentIndex(Config.ACTION_ALLOW_IDX) + self.prefs.comboUITarget.setCurrentIndex(2) + self.prefs.comboUIDuration.setCurrentIndex(4) + self.prefs.comboUIDialogPos.setCurrentIndex(2) + self.prefs.spinUITimeout.setValue(30) + self.prefs.showAdvancedCheck.setChecked(True) + self.prefs.uidCheck.setChecked(True) + + self.run(qtbot) + + assert self.prefs._cfg.getInt(self.prefs._cfg.DEFAULT_ACTION_KEY) == Config.ACTION_ALLOW_IDX and self.prefs.comboUIAction.currentText() == Config.ACTION_ALLOW + assert self.prefs._cfg.getInt(self.prefs._cfg.DEFAULT_TARGET_KEY) == 2 + assert self.prefs._cfg.getInt(self.prefs._cfg.DEFAULT_DURATION_KEY) == 4 + assert self.prefs._cfg.getInt(self.prefs._cfg.DEFAULT_TIMEOUT_KEY) == 30 + assert self.prefs._cfg.getInt(self.prefs._cfg.DEFAULT_POPUP_POSITION) == 2 + assert self.prefs._cfg.getBool(self.prefs._cfg.DEFAULT_POPUP_ADVANCED) == True + assert self.prefs._cfg.getBool(self.prefs._cfg.DEFAULT_POPUP_ADVANCED_UID) == True + + def test_save_ui_settings(self, qtbot): + self.prefs.checkUIRules.setChecked(True) + self.prefs.comboUIRules.setCurrentIndex(1) + self.prefs.checkHideNode.setChecked(False) + self.prefs.checkHideProto.setChecked(False) + + self.run(qtbot) + + assert self.prefs._cfg.getBool(self.prefs._cfg.DEFAULT_IGNORE_RULES) == True and self.prefs._cfg.getInt(self.prefs._cfg.DEFAULT_IGNORE_TEMPORARY_RULES) == 1 + cols = self.prefs._cfg.getSettings(Config.STATS_SHOW_COLUMNS) + assert cols == ['0','2','3','5','6'] + + def test_save_node_settings(self, qtbot, capsys): + self.prefs.comboNodeAction.setCurrentIndex(Config.ACTION_ALLOW_IDX) + self.prefs.comboNodeMonitorMethod.setCurrentIndex(2) + self.prefs.comboNodeLogLevel.setCurrentIndex(5) + self.prefs.checkInterceptUnknown.setChecked(True) + self.prefs.tabWidget.setCurrentIndex(self.prefs.TAB_NODES) + self.prefs._node_needs_update = True + + self.run(qtbot) + + assert len(self.prefs._notifications_sent) == 1 + for n in self.prefs._notifications_sent: + conf = json.loads(self.prefs._notifications_sent[n].data) + assert conf['InterceptUnknown'] == True + assert conf['ProcMonitorMethod'] == "audit" + assert conf['LogLevel'] == 5 + assert conf['DefaultAction'] == "allow" + +# TODO: click on the QMessageDialog +# +# def test_save_db_settings(self, qtbot, monkeypatch, capsys): +# self.prefs.comboDBType.setCurrentIndex(1) +# self.prefs.dbLabel.setText('/tmp/test.db') +# +# def handle_dialog(): +# qtbot.mouseClick(self.prefs.applyButton, QtCore.Qt.LeftButton) +# # after saving the settings, a warning dialog must appear, informing +# # the user to restart the GUI +# time.sleep(.5) +# msgbox = QtWidgets.QApplication.activeModalWidget() +# try: +# assert msgbox != None +# okBtn = msgbox.button(QtWidgets.QMessageBox.Ok) +# qtbot.mouseClick(okBtn, QtCore.Qt.LeftButton) +# except Exception as e: +# print("test_save_db_Settings() exception:", e) +# qtbot.mouseClick(self.prefs.acceptButton, QtCore.Qt.LeftButton) +# +# QtCore.QTimer.singleShot(500, handle_dialog) +# self.prefs.exec_() + +# assert self.prefs._cfg.getInt(Config.DEFAULT_DB_TYPE_KEY) == 1 +# assert self.prefs._cfg.getSettings(Config.DEFAULT_DB_FILE_KEY) == '/tmp/test.db' + + def test_load_ui_settings(self, qtbot, capsys): + """ reTest saved settings (load_settings()). + On dialog show up the widgets must be configured properly, with the settings + configured in previous tests. + """ + self.prefs.checkUIRules.setChecked(False) + self.prefs.comboUIRules.setCurrentIndex(0) + self.prefs.comboUITarget.setCurrentIndex(0) + self.prefs.comboUIDuration.setCurrentIndex(0) + self.prefs.checkHideNode.setChecked(True) + self.prefs.checkHideProto.setChecked(True) + + def handle_dialog(): + qtbot.mouseClick(self.prefs.cancelButton, QtCore.Qt.LeftButton) + QtCore.QTimer.singleShot(500, handle_dialog) + + self.prefs.exec_() + self.prefs.show() + + print(self.prefs._cfg.getBool(self.prefs._cfg.DEFAULT_IGNORE_RULES)) + + assert self.prefs.comboUIAction.currentIndex() == Config.ACTION_ALLOW_IDX and self.prefs.comboUIAction.currentText() == Config.ACTION_ALLOW + assert self.prefs.checkUIRules.isChecked() == True + assert self.prefs.comboUIRules.currentIndex() == 1 + assert self.prefs.comboUITarget.currentIndex() == 2 + assert self.prefs.comboUIDuration.currentIndex() == 4 and self.prefs.comboUIDuration.currentText() == Config.DURATION_30m + assert self.prefs.comboUIDialogPos.currentIndex() == 2 + assert self.prefs.spinUITimeout.value() == 30 diff --git a/ui/tests/dialogs/test_ruleseditor.py b/ui/tests/dialogs/test_ruleseditor.py new file mode 100644 index 0000000..2b06bd1 --- /dev/null +++ b/ui/tests/dialogs/test_ruleseditor.py @@ -0,0 +1,384 @@ +# +# pytest -v tests/dialogs/test_ruleseditor.py +# + +import json +from PyQt5 import QtCore, QtWidgets, QtGui + +from opensnitch.config import Config +from opensnitch.dialogs.ruleseditor import RulesEditorDialog + +class TestRulesEditor(): + + @classmethod + def setup_method(self): + white_icon = QtGui.QIcon("../res/icon-white.svg") + self.rd = RulesEditorDialog(appicon=white_icon) + self.rd.show() + self.rd.ruleNameEdit.setText("xxx") + self.rd.nodesCombo.addItem("unix:/tmp/osui.sock") + self.rd.nodesCombo.setCurrentText("unix:/tmp/osui.sock") + self.rd._nodes._nodes["unix:/tmp/osui.sock"] = {} + + def test_rule_no_fields(self, qtbot): + """ Test that rules without fields selected cannot be created. + """ + qtbot.addWidget(self.rd) + + def handle_dialog(): + qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply), QtCore.Qt.LeftButton) + qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Close), QtCore.Qt.LeftButton) + + QtCore.QTimer.singleShot(100, handle_dialog) + self.rd.exec_() + assert self.rd.statusLabel.text() != "" + + def test_fields_empty(self, qtbot): + """ Test that fields cannot be empty. + """ + + self.rd.pidCheck.setChecked(True) + self.rd.pidLine.setText("") + result, error = self.rd._save_rule() + assert error != None + + self.rd.pidCheck.setChecked(False) + self.rd.uidCheck.setChecked(True) + self.rd.uidLine.setText("") + result, error = self.rd._save_rule() + assert error != None + + self.rd.uidCheck.setChecked(False) + self.rd.procCheck.setChecked(True) + self.rd.procLine.setText("") + result, error = self.rd._save_rule() + assert error != None + + self.rd.procCheck.setChecked(False) + self.rd.cmdlineCheck.setChecked(True) + self.rd.cmdlineLine.setText("") + result, error = self.rd._save_rule() + assert error != None + + self.rd.cmdlineCheck.setChecked(False) + self.rd.dstPortCheck.setChecked(True) + self.rd.dstPortLine.setText("") + result, error = self.rd._save_rule() + assert error != None + + self.rd.dstPortCheck.setChecked(False) + self.rd.dstHostCheck.setChecked(True) + self.rd.dstHostLine.setText("") + result, error = self.rd._save_rule() + assert error != None + + self.rd.dstHostCheck.setChecked(False) + self.rd.dstListsCheck.setChecked(True) + self.rd.dstListsLine.setText("") + result, error = self.rd._save_rule() + assert error != None + + def test_add_basic_rule(self, qtbot): + """ Test adding a basic rule. + """ + + self.rd.statusLabel.setText("") + self.rd.ruleNameEdit.setText("www.test.com") + self.rd.dstHostCheck.setChecked(True) + self.rd.dstHostLine.setText("www.test.com") + self.rd.durationCombo.setCurrentIndex(self.rd._load_duration(Config.DURATION_UNTIL_RESTART)) + + def handle_dialog(): + qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply), QtCore.Qt.LeftButton) + qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Close), QtCore.Qt.LeftButton) + + QtCore.QTimer.singleShot(100, handle_dialog) + self.rd.exec_() + + assert self.rd.statusLabel.text() == "" + assert self.rd._db.get_rule("www.test.com", self.rd.nodesCombo.currentText()).next() == True + assert self.rd._old_rule_name == "www.test.com" + # after adding a rule, we enter into editing mode, to allow editing it + # without closing the dialog. + assert self.rd.WORK_MODE == self.rd.EDIT_RULE + assert self.rd.rule.operator.type == Config.RULE_TYPE_SIMPLE + assert self.rd.rule.operator.operand == "dest.host" + assert self.rd.rule.operator.data == "www.test.com" + assert self.rd.rule.duration == Config.DURATION_UNTIL_RESTART + + def test_add_complex_rule(self, qtbot): + """ Test add complex rule. + """ + self.rd.WORK_MODE = self.rd.ADD_RULE + self.rd._reset_state() + self.rd.statusLabel.setText("") + self.rd.ruleNameEdit.setText("www.test-complex.com") + self.rd.dstHostCheck.setChecked(True) + self.rd.dstHostLine.setText("www.test-complex.com") + self.rd.dstPortCheck.setChecked(True) + self.rd.dstPortLine.setText("443") + + def handle_dialog(): + qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply), QtCore.Qt.LeftButton) + qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Close), QtCore.Qt.LeftButton) + + QtCore.QTimer.singleShot(100, handle_dialog) + self.rd.exec_() + + assert self.rd.statusLabel.text() == "" + assert self.rd._db.get_rule("www.test-complex.com", self.rd.nodesCombo.currentText()).next() == True + assert self.rd._old_rule_name == "www.test-complex.com" + # after adding a rule, we enter into editing mode, to allow editing it + # without closing the dialog. + assert self.rd.WORK_MODE == self.rd.EDIT_RULE + assert self.rd.rule.operator.type == Config.RULE_TYPE_LIST + assert self.rd.rule.operator.operand == Config.RULE_TYPE_LIST + json_rule = json.loads(self.rd.rule.operator.data) + assert json_rule[0]['type'] == "simple" + assert json_rule[0]['operand'] == "dest.port" + assert json_rule[0]['data'] == "443" + assert json_rule[1]['type'] == "simple" + assert json_rule[1]['operand'] == "dest.host" + assert json_rule[1]['data'] == "www.test-complex.com" + + def test_add_reject_rule(self, qtbot): + """ Test adding new rule with action "reject". + """ + + self.rd.statusLabel.setText("") + self.rd.ruleNameEdit.setText("www.test-reject.com") + self.rd.dstHostCheck.setChecked(True) + self.rd.dstHostLine.setText("www.test-reject.com") + self.rd.actionRejectRadio.setChecked(True) + + def handle_dialog(): + qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply), QtCore.Qt.LeftButton) + qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Close), QtCore.Qt.LeftButton) + + QtCore.QTimer.singleShot(100, handle_dialog) + self.rd.exec_() + + assert self.rd.statusLabel.text() == "" + assert self.rd._db.get_rule("www.test-reject.com", self.rd.nodesCombo.currentText()).next() == True + assert self.rd._old_rule_name == "www.test-reject.com" + # after adding a rule, we enter into editing mode, to allow editing it + # without closing the dialog. + assert self.rd.WORK_MODE == self.rd.EDIT_RULE + assert self.rd.rule.operator.type == Config.RULE_TYPE_SIMPLE + assert self.rd.rule.operator.operand == "dest.host" + assert self.rd.rule.operator.data == "www.test-reject.com" + assert self.rd.rule.action == Config.ACTION_REJECT + + def test_add_deny_rule(self, qtbot): + """ Test adding new rule with action "deny". + """ + + self.rd.statusLabel.setText("") + self.rd.ruleNameEdit.setText("www.test-deny.com") + self.rd.dstHostCheck.setChecked(True) + self.rd.dstHostLine.setText("www.test-deny.com") + self.rd.actionDenyRadio.setChecked(True) + + def handle_dialog(): + qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply), QtCore.Qt.LeftButton) + qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Close), QtCore.Qt.LeftButton) + + QtCore.QTimer.singleShot(100, handle_dialog) + self.rd.exec_() + + assert self.rd.statusLabel.text() == "" + assert self.rd._db.get_rule("www.test-deny.com", self.rd.nodesCombo.currentText()).next() == True + assert self.rd._old_rule_name == "www.test-deny.com" + # after adding a rule, we enter into editing mode, to allow editing it + # without closing the dialog. + assert self.rd.WORK_MODE == self.rd.EDIT_RULE + assert self.rd.rule.operator.type == Config.RULE_TYPE_SIMPLE + assert self.rd.rule.operator.operand == "dest.host" + assert self.rd.rule.operator.data == "www.test-deny.com" + assert self.rd.rule.action == Config.ACTION_DENY + + def test_add_allow_rule(self, qtbot): + """ Test adding new rule with action "allow". + """ + + self.rd.statusLabel.setText("") + self.rd.ruleNameEdit.setText("www.test-allow.com") + self.rd.dstHostCheck.setChecked(True) + self.rd.dstHostLine.setText("www.test-allow.com") + self.rd.actionAllowRadio.setChecked(True) + + def handle_dialog(): + qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply), QtCore.Qt.LeftButton) + qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Close), QtCore.Qt.LeftButton) + + QtCore.QTimer.singleShot(100, handle_dialog) + self.rd.exec_() + + assert self.rd.statusLabel.text() == "" + assert self.rd._db.get_rule("www.test-allow.com", self.rd.nodesCombo.currentText()).next() == True + assert self.rd._old_rule_name == "www.test-allow.com" + # after adding a rule, we enter into editing mode, to allow editing it + # without closing the dialog. + assert self.rd.WORK_MODE == self.rd.EDIT_RULE + assert self.rd.rule.operator.type == Config.RULE_TYPE_SIMPLE + assert self.rd.rule.operator.operand == "dest.host" + assert self.rd.rule.operator.data == "www.test-allow.com" + assert self.rd.rule.action == Config.ACTION_ALLOW + + def test_add_rule_name_conflict(self, qtbot): + """ Test that rules with the same name cannot be added. + """ + assert self.rd._db.get_rule("www.test.com", self.rd.nodesCombo.currentText()).next() == True + + self.rd.statusLabel.setText("") + self.rd.ruleNameEdit.setText("www.test.com") + self.rd.dstHostCheck.setChecked(True) + self.rd.dstHostLine.setText("www.test.com") + + def handle_dialog(): + qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply), QtCore.Qt.LeftButton) + qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Close), QtCore.Qt.LeftButton) + + QtCore.QTimer.singleShot(100, handle_dialog) + self.rd.exec_() + + assert self.rd.statusLabel.text() != "" + + def test_load_rule(self, qtbot): + """ Test loading a rule. + """ + self.rd.WORK_MODE = self.rd.ADD_RULE + self.rd._reset_state() + records = self.rd._db.get_rule("www.test.com", self.rd.nodesCombo.currentText()) + assert records.next() == True + + self.rd.edit_rule(records, self.rd.nodesCombo.currentText()) + assert self.rd.WORK_MODE == self.rd.EDIT_RULE + assert self.rd.ruleNameEdit.text() == "www.test.com" + assert self.rd.dstHostCheck.isChecked() == True + assert self.rd.dstHostLine.text() == "www.test.com" + assert self.rd.durationCombo.currentIndex() == self.rd._load_duration(Config.DURATION_UNTIL_RESTART) + + def handle_dialog(): + qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Close), QtCore.Qt.LeftButton) + + QtCore.QTimer.singleShot(100, handle_dialog) + self.rd.exec_() + + def test_edit_and_rename_rule(self, qtbot): + """ Test loading, editing and renaming a rule. + """ + self.rd.WORK_MODE = self.rd.ADD_RULE + self.rd._reset_state() + records = self.rd._db.get_rule("www.test.com", self.rd.nodesCombo.currentText()) + assert records.next() == True + + self.rd.edit_rule(records, self.rd.nodesCombo.currentText()) + assert self.rd.WORK_MODE == self.rd.EDIT_RULE + assert self.rd.ruleNameEdit.text() == "www.test.com" + assert self.rd.dstHostCheck.isChecked() == True + assert self.rd.dstHostLine.text() == "www.test.com" + + self.rd.ruleNameEdit.setText("www.test-renamed.com") + self.rd.dstHostLine.setText("www.test-renamed.com") + + def handle_dialog(): + qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply), QtCore.Qt.LeftButton) + qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Close), QtCore.Qt.LeftButton) + + QtCore.QTimer.singleShot(100, handle_dialog) + self.rd.exec_() + + records = self.rd._db.get_rule("www.test.com", self.rd.nodesCombo.currentText()) + assert records.next() == False + records = self.rd._db.get_rule("www.test-renamed.com", self.rd.nodesCombo.currentText()) + assert records.next() == True + + def test_durations(self, qtbot): + """ Test adding new rule with action "deny". + """ + + self.rd.statusLabel.setText("") + self.rd.ruleNameEdit.setText("www.test-duration.com") + self.rd.dstHostCheck.setChecked(True) + self.rd.dstHostLine.setText("www.test-duration.com") + self.rd.actionDenyRadio.setChecked(True) + self.rd.durationCombo.setCurrentIndex(self.rd._load_duration(Config.DURATION_ALWAYS)) + + def handle_dialog(): + qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply), QtCore.Qt.LeftButton) + qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Close), QtCore.Qt.LeftButton) + + QtCore.QTimer.singleShot(100, handle_dialog) + self.rd.exec_() + + assert self.rd.statusLabel.text() == "" + assert self.rd._db.get_rule("www.test-duration.com", self.rd.nodesCombo.currentText()).next() == True + assert self.rd._old_rule_name == "www.test-duration.com" + # after adding a rule, we enter into editing mode, to allow editing it + # without closing the dialog. + assert self.rd.WORK_MODE == self.rd.EDIT_RULE + assert self.rd.rule.operator.type == Config.RULE_TYPE_SIMPLE + assert self.rd.rule.operator.operand == "dest.host" + assert self.rd.rule.operator.data == "www.test-duration.com" + assert self.rd.rule.action == Config.ACTION_DENY + assert self.rd.rule.duration == Config.DURATION_ALWAYS + + def test_rule_LANs(self, qtbot): + """ Test rule with regexp and LAN keyword in particular. + """ + self.rd.statusLabel.setText("") + self.rd.ruleNameEdit.setText("www.test-rule-LAN.com") + self.rd.dstIPCheck.setChecked(True) + self.rd.dstIPCombo.setCurrentText(self.rd.LAN_LABEL) + self.rd.actionDenyRadio.setChecked(True) + self.rd.durationCombo.setCurrentIndex(self.rd._load_duration(Config.DURATION_ALWAYS)) + + def handle_dialog(): + qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply), QtCore.Qt.LeftButton) + qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Close), QtCore.Qt.LeftButton) + + QtCore.QTimer.singleShot(100, handle_dialog) + self.rd.exec_() + + assert self.rd.statusLabel.text() == "" + assert self.rd._db.get_rule("www.test-rule-LAN.com", self.rd.nodesCombo.currentText()).next() == True + assert self.rd._old_rule_name == "www.test-rule-LAN.com" + # after adding a rule, we enter into editing mode, to allow editing it + # without closing the dialog. + assert self.rd.WORK_MODE == self.rd.EDIT_RULE + assert self.rd.rule.operator.type == Config.RULE_TYPE_REGEXP + assert self.rd.rule.operator.operand == "dest.ip" + assert self.rd.rule.operator.data == self.rd.LAN_RANGES + assert self.rd.rule.action == Config.ACTION_DENY + assert self.rd.rule.duration == Config.DURATION_ALWAYS + + def test_rule_networks(self, qtbot): + """ Test rule with networks. + """ + self.rd.statusLabel.setText("") + self.rd.ruleNameEdit.setText("www.test-rule-networks.com") + self.rd.dstIPCheck.setChecked(True) + self.rd.dstIPCombo.setCurrentText("192.168.111.0/24") + self.rd.actionDenyRadio.setChecked(True) + self.rd.durationCombo.setCurrentIndex(self.rd._load_duration(Config.DURATION_ALWAYS)) + + def handle_dialog(): + qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply), QtCore.Qt.LeftButton) + qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Close), QtCore.Qt.LeftButton) + + QtCore.QTimer.singleShot(100, handle_dialog) + self.rd.exec_() + + assert self.rd.statusLabel.text() == "" + assert self.rd._db.get_rule("www.test-rule-networks.com", self.rd.nodesCombo.currentText()).next() == True + assert self.rd._old_rule_name == "www.test-rule-networks.com" + # after adding a rule, we enter into editing mode, to allow editing it + # without closing the dialog. + assert self.rd.WORK_MODE == self.rd.EDIT_RULE + assert self.rd.rule.operator.type == Config.RULE_TYPE_NETWORK + assert self.rd.rule.operator.operand == "dest.network" + assert self.rd.rule.operator.data == "192.168.111.0/24" + assert self.rd.rule.action == Config.ACTION_DENY + assert self.rd.rule.duration == Config.DURATION_ALWAYS + diff --git a/ui/tests/test_nodes.py b/ui/tests/test_nodes.py new file mode 100644 index 0000000..d4c4ae5 --- /dev/null +++ b/ui/tests/test_nodes.py @@ -0,0 +1,149 @@ +# +# pytest -v tests/nodes.py +# + +import json +from PyQt5 import QtCore +from opensnitch import ui_pb2 +from opensnitch.config import Config +from opensnitch.nodes import Nodes +from tests.dialogs import ClientConfig + +class NotifTest(QtCore.QObject): + """We need to subclass from QObject in order to be able to user signals and slots. + """ + signal = QtCore.pyqtSignal(ui_pb2.NotificationReply) + + @QtCore.pyqtSlot(ui_pb2.NotificationReply) + def callback(self, reply): + assert reply != None + assert reply.code == ui_pb2.OK and reply.type == ui_pb2.LOAD_FIREWALL and reply.data == "test" + + +class TestNodes(): + + @classmethod + def setup_method(self): + self.nid = None + self.daemon_config = ClientConfig + self.nodes = Nodes.instance() + self.nodes._db.insert("nodes", + "(addr, status, hostname, daemon_version, daemon_uptime, " \ + "daemon_rules, cons, cons_dropped, version, last_connection)", + ( + "1.2.3.4", Nodes.ONLINE, "xxx", "v1.2.3", str(0), + "", "0", "0", "", + "2022-01-03 11:22:48.101624" + ) + ) + + + def test_add(self, qtbot): + node = self.nodes.add("peer:1.2.3.4", self.daemon_config) + + assert node != None + + def test_get_node(self, qtbot): + node = self.nodes.get_node("peer:1.2.3.4") + + assert node != None + + def test_get_addr(self, qtbot): + proto, addr = self.nodes.get_addr("peer:1.2.3.4") + + assert proto == "peer" and addr == "1.2.3.4" + + def test_get_nodes(self, qtbot): + nodes = self.nodes.get_nodes() + print(nodes) + + assert nodes.get("peer:1.2.3.4") != None + + def test_add_rule(self, qtbot): + self.nodes.add_rule( + "2022-01-03 11:22:48.101624", + "peer:1.2.3.4", + "test", + True, + False, + Config.ACTION_ALLOW, Config.DURATION_30s, + Config.RULE_TYPE_SIMPLE, False, "dest.host", "" + ) + + query = self.nodes._db.get_rule("test", "peer:1.2.3.4") + + assert query.first() == True + assert query.record().value(0) == "2022-01-03 11:22:48.101624" + assert query.record().value(1) == "peer:1.2.3.4" + assert query.record().value(2) == "test" + assert query.record().value(3) == "1" + assert query.record().value(4) == "0" + assert query.record().value(5) == Config.ACTION_ALLOW + assert query.record().value(6) == Config.DURATION_30s + assert query.record().value(7) == Config.RULE_TYPE_SIMPLE + assert query.record().value(8) == "0" + assert query.record().value(9) == "dest.host" + + def test_update_rule_time(self, qtbot): + query = self.nodes._db.get_rule("test", "peer:1.2.3.4") + assert query.first() == True + assert query.record().value(0) == "2022-01-03 11:22:48.101624" + + self.nodes.update_rule_time("2022-01-03 21:22:48.101624", "test", "peer:1.2.3.4") + query = self.nodes._db.get_rule("test", "peer:1.2.3.4") + assert query.first() == True + assert query.record().value(0) == "2022-01-03 21:22:48.101624" + + def test_delete_rule(self, qtbot): + query = self.nodes._db.get_rule("test", "peer:1.2.3.4") + assert query.first() == True + + self.nodes.delete_rule("test", "peer:1.2.3.4", None) + query = self.nodes._db.get_rule("test", "peer:1.2.3.4") + assert query.first() == False + + def test_update_node_status(self, qtbot): + query = self.nodes._db.select("SELECT status FROM nodes WHERE addr = '{0}'".format("1.2.3.4")) + assert query != None and query.exec_() == True and query.first() == True + assert query.record().value(0) == Nodes.ONLINE + + self.nodes.update("peer", "1.2.3.4", Nodes.OFFLINE) + query = self.nodes._db.select("SELECT status FROM nodes WHERE addr = '{0}'".format("1.2.3.4")) + assert query != None and query.exec_() == True and query.first() == True + assert query.record().value(0) == Nodes.OFFLINE + + def test_send_notification(self, qtbot): + notifs = NotifTest() + notifs.signal.connect(notifs.callback) + + test_notif = ui_pb2.Notification( + clientName="", + serverName="", + type=ui_pb2.LOAD_FIREWALL, + data="test", + rules=[]) + + self.nid = self.nodes.send_notification("peer:1.2.3.4", test_notif, notifs.signal) + assert self.nodes._notifications_sent[self.nid] != None + assert self.nodes._notifications_sent[self.nid]['type'] == ui_pb2.LOAD_FIREWALL + + def test_reply_notification(self, qtbot): + reply_notif = ui_pb2.Notification( + id = self.nid, + clientName="", + serverName="", + type=ui_pb2.LOAD_FIREWALL, + data="test", + rules=[]) + # just after process the reply, the notification is deleted (except if + # is of type MONITOR_PROCESS + self.nodes.reply_notification("peer:1.2.3.4", reply_notif) + assert self.nid not in self.nodes._notifications_sent + + def test_delete(self, qtbot): + self.nodes.delete("peer:1.2.3.4") + node = self.nodes.get_node("peer:1.2.3.4") + nodes = self.nodes.get_nodes() + + assert node == None + assert nodes.get("peer:1.2.3.4") == None diff --git a/utils/legacy/make_ads_rules.py b/utils/legacy/make_ads_rules.py new file mode 100644 index 0000000..17ef929 --- /dev/null +++ b/utils/legacy/make_ads_rules.py @@ -0,0 +1,69 @@ +import requests +import re +import ipaddress +import datetime +import os + +lists = ( \ + "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts", + "https://mirror1.malwaredomains.com/files/justdomains", + "http://sysctl.org/cameleon/hosts", + "https://zeustracker.abuse.ch/blocklist.php?download=domainblocklist", + "https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt", + "https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt", + "https://hosts-file.net/ad_servers.txt" ) + +domains = {} + +for url in lists: + print "Downloading %s ..." % url + r = requests.get(url) + if r.status_code != 200: + print "Error, status code %d" % r.status_code + continue + + for line in r.text.split("\n"): + line = line.strip() + if line == "": + continue + + elif line[0] == "#": + continue + + for part in re.split(r'\s+', line): + part = part.strip() + if part == "": + continue + + try: + duh = ipaddress.ip_address(part) + except ValueError: + if part != "localhost": + domains[part] = 1 + +print "Got %d unique domains, saving as rules to ./rules/ ..." % len(domains) + +os.system("mkdir -p rules") + +idx = 0 +for domain, _ in domains.iteritems(): + with open("rules/adv-%d.json" % idx, "wt") as fp: + tpl = """ +{ + "created": "%s", + "updated": "%s", + "name": "deny-adv-%d", + "enabled": true, + "action": "deny", + "duration": "always", + "operator": { + "type": "simple", + "operand": "dest.host", + "data": "%s" + } +}""" + now = datetime.datetime.utcnow().isoformat("T") + "Z" + data = tpl % ( now, now, idx, domain ) + fp.write(data) + + idx = idx + 1 diff --git a/utils/scripts/ads/update_adlists.sh b/utils/scripts/ads/update_adlists.sh new file mode 100755 index 0000000..59f92f6 --- /dev/null +++ b/utils/scripts/ads/update_adlists.sh @@ -0,0 +1,91 @@ +#!/bin/bash +# opensnitch - 2021 +# +# https://github.com/evilsocket/opensnitch/wiki/block-lists +# +# Add the script to a regular user's crontab: +# $ crontab -e +# 0 11,17,23 * * * /home/user/scripts/update_adlists.sh + +# https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=0&mimetype=plaintext +# https://hostfiles.frogeye.fr/multiparty-trackers-hosts.txt +# https://hostfiles.frogeye.fr/firstparty-trackers-hosts.txt +# https://adaway.org/hosts.txt +# https://www.github.developerdan.com/hosts/lists/tracking-aggressive-extended.txt +# https://raw.githubusercontent.com/Kees1958/W3C_annual_most_used_survey_blocklist/master/TOP_EU_US_Ads_Trackers_HOST +# https://curben.gitlab.io/malware-filter/urlhaus-filter-hosts.txt + +# Use any directory you want to save the lists. +# If you use /etc/opensnitchd, give write permissions to blocklists/* for your user (chown -R /etc/opensnitchd/blocklists/). +# or use a directory from your user's home. +adsDir="/etc/opensnitchd/blocklists/domains/" + +# If you add new urls, remember to add the corresponding filename where it'll be save on disk. +adsList=( + "https://curben.gitlab.io/malware-filter/urlhaus-filter-hosts.txt" + "https://raw.githubusercontent.com/Kees1958/W3C_annual_most_used_survey_blocklist/master/TOP_EU_US_Ads_Trackers_HOST" + "https://hostfiles.frogeye.fr/multiparty-trackers-hosts.txt" + "https://hostfiles.frogeye.fr/firstparty-trackers-hosts.txt" + "https://www.github.developerdan.com/hosts/lists/tracking-aggressive-extended.txt" + "https://adaway.org/hosts.txt" + "https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=0&mimetype=plaintex") +adsListNames=( + "urlhaus-filter-hosts.txt" + "top_eu_us_ads_trackers.txt" + "multiparty-trackers-hosts.txt" + "firstparty-trackers-hosts.txt" + "tracking-aggressive-extended.txt" + "adaway-hosts.txt" + "yoyo-adservers.txt") + +function download_cname_trackers +{ + remoteSize=$(curl --silent -I https://raw.githubusercontent.com/AdguardTeam/AdguardFilters/master/SpywareFilter/sections/cname_trackers.txt | awk '/content-length:/ {print $2}'|tr -d '\r') + localSize=$(stat -c %s $adsDir/cname_trackers.txt) + if [ ! -z $remoteSize ]; then + if [ "$remoteSize" != "$localSize" ]; then + > /tmp/.cname_temp + cname_trackers=$(curl --silent https://raw.githubusercontent.com/AdguardTeam/AdguardFilters/master/SpywareFilter/sections/cname_trackers.txt | awk '/^!#include/ { print $2 }') + for tracker in $cname_trackers + do + curl --silent $tracker | grep "^||" | sed 's/^||\(.*\)^/0.0.0.0 \1/' >> /tmp/.cname_temp + done + mv /tmp/.cname_temp $adsDir/cname_trackers.txt + else + echo "[-] cname trackers not updated yet" + fi + fi +} + +function download_ads_list +{ + reload=0 + for idx in ${!adsList[@]} + do + echo "[+] Checking list ${adsList[$idx]}, ${adsListNames[$idx]}" + remoteSize=$(curl --silent -I ${adsList[$idx]}|awk '/content-length:/ {print $2}'|tr -d '\r') + localSize=$(stat -c %s $adsDir/${adsListNames[$idx]}) + + if [ ! -z $remoteSize ]; then + if [ "$remoteSize" != "$localSize" ]; then + echo "[+] downloading new ads list... ${adsList[$idx]}, $remoteSize, $localSize" + curl --silent "${adsList[$idx]}" -o $adsDir/${adsListNames[$idx]} + reload=1 + else + echo "[-] ads list not updated yet: $remoteSize, $localSize - ${adsList[$idx]}" + fi + else + echo "[!] No content-length header found: ${adsList[$idx]}" + fi + done +} + + +if [ ! -d $adsDir ]; then + mkdir -p $adsDir +fi + +cd $adsDir + +download_ads_list +download_cname_trackers -- 2.30.2